From: Mathieu Date: Tue, 6 Dec 2022 05:47:19 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/merge-to-testing' into testing X-Git-Tag: v2.1.107~2 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=3b45f571938e0eb6803084aac3f2bd298e6026ba;hp=a929fb8039cfec511d9bdce55255e709c5af1fd4;p=lgpl%2Fargeo-commons.git Merge remote-tracking branch 'origin/merge-to-testing' into testing --- diff --git a/.gitignore b/.gitignore index c306476f0..11caf4780 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ **/bin/ -**/target/ -**/generated/ **/MANIFEST.MF -/build/ /sdk.mk /output/ diff --git a/Makefile b/Makefile index 49a995426..330bc4fca 100644 --- a/Makefile +++ b/Makefile @@ -1,62 +1,53 @@ include sdk.mk -.PHONY: clean all osgi jni +.PHONY: clean all osgi -all: osgi jni move-rap - $(MAKE) -f Makefile-rcp.mk - -move-rap: - mkdir -p $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap - mv -v $(A2_OUTPUT)/$(A2_CATEGORY)/*.rap.$(MAJOR).$(MINOR).jar $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap - touch $(BUILD_BASE)/*.rap/bnd.bnd +all: osgi + $(MAKE) -f Makefile-rcp.mk all A2_CATEGORY = org.argeo.cms BUNDLES = \ org.argeo.init \ -org.argeo.util \ org.argeo.api.uuid \ +org.argeo.api.register \ org.argeo.api.acr \ +org.argeo.api.cli \ org.argeo.api.cms \ org.argeo.cms \ -org.argeo.cms.pgsql \ -eclipse/org.argeo.cms.servlet \ -eclipse/org.argeo.cms.swt \ -eclipse/org.argeo.cms.e4 \ -rap/org.argeo.cms.ui.rap \ -rap/org.argeo.swt.specific.rap \ -rap/org.argeo.cms.e4.rap \ -jcr/org.argeo.cms.jcr \ -jcr/org.argeo.cms.ui \ - -JAVADOC_BUNDLES = \ -org.argeo.api.uuid \ -org.argeo.api.acr \ -org.argeo.api.cms - -JAVADOC_PACKAGES = \ -org.argeo.api.uuid \ -org.argeo.api.acr \ -org.argeo.api.cms - -A2_OUTPUT = $(SDK_BUILD_BASE)/a2 -A2_BASE = $(A2_OUTPUT) - -VPATH = .:eclipse:rap:jcr +org.argeo.cms.ux \ +org.argeo.cms.ee \ +org.argeo.cms.lib.jetty \ +org.argeo.cms.lib.sshd \ +org.argeo.cms.cli \ +osgi/equinox/org.argeo.cms.lib.equinox \ +swt/org.argeo.swt.minidesktop \ +swt/org.argeo.cms.swt \ +swt/org.argeo.cms.e4 \ +swt/rap/org.argeo.swt.specific.rap \ +swt/rap/org.argeo.cms.swt.rap \ +swt/rap/org.argeo.cms.e4.rap \ DEP_CATEGORIES = \ org.argeo.tp \ -org.argeo.tp.apache \ +org.argeo.tp.crypto \ org.argeo.tp.jetty \ -org.argeo.tp.eclipse.equinox \ -org.argeo.tp.eclipse.rap \ -org.argeo.tp.jcr +osgi/api/org.argeo.tp.osgi \ +osgi/equinox/org.argeo.tp.eclipse \ +swt/rap/org.argeo.tp.swt \ +swt/rap/org.argeo.tp.swt.workbench \ +$(A2_CATEGORY) \ +swt/$(A2_CATEGORY) \ +swt/rap/$(A2_CATEGORY) \ -jni: - $(MAKE) -C jni +JAVADOC_PACKAGES = \ +org.argeo.api.uuid \ +org.argeo.api.acr \ +org.argeo.api.cms clean: rm -rf $(BUILD_BASE) - $(MAKE) -C jni clean $(MAKE) -f Makefile-rcp.mk clean +A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES))) + include $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk \ No newline at end of file diff --git a/Makefile-rcp.mk b/Makefile-rcp.mk index 8811d2448..29f1a23a3 100644 --- a/Makefile-rcp.mk +++ b/Makefile-rcp.mk @@ -1,32 +1,31 @@ include sdk.mk .PHONY: clean all osgi -all: osgi +all: osgi -A2_CATEGORY = org.argeo.cms.eclipse.rcp +A2_CATEGORY = org.argeo.cms BUNDLES = \ -rcp/org.argeo.cms.e4.rcp \ -rcp/org.argeo.cms.ui.rcp \ -rcp/org.argeo.swt.minidesktop \ -rcp/org.argeo.swt.specific.rcp \ - -A2_OUTPUT = $(SDK_BUILD_BASE)/a2 -A2_BASE = $(A2_OUTPUT) +swt/rcp/org.argeo.swt.specific.rcp \ +swt/rcp/org.argeo.cms.swt.rcp \ +swt/rcp/org.argeo.cms.e4.rcp \ DEP_CATEGORIES = \ org.argeo.cms \ +swt/org.argeo.cms \ org.argeo.tp \ -org.argeo.tp.apache \ +org.argeo.tp.crypto \ org.argeo.tp.jetty \ -org.argeo.tp.eclipse.equinox \ -org.argeo.tp.eclipse.rcp \ -org.argeo.tp.jcr +osgi/equinox/org.argeo.tp.eclipse \ +osgi/api/org.argeo.tp.osgi \ +swt/rcp/org.argeo.tp.swt \ +lib/linux/x86_64/swt/rcp/org.argeo.tp.swt \ +swt/rcp/org.argeo.tp.swt.workbench \ clean: rm -rf $(BUILD_BASE) -VPATH = .:rcp +VPATH = .:swt/rcp include $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk \ No newline at end of file diff --git a/branch.mk b/branch.mk index 936a67569..dbecaaa4b 100644 --- a/branch.mk +++ b/branch.mk @@ -1 +1 @@ -include $(SDK_SRC_BASE)/cnf/testing.bnd +BRANCH=testing \ No newline at end of file diff --git a/cnf/build.bnd b/cnf/build.bnd deleted file mode 100644 index a464edc1f..000000000 --- a/cnf/build.bnd +++ /dev/null @@ -1,3 +0,0 @@ --include: \ -${workspace}/cnf/testing.bnd, \ -${workspace}/sdk/argeo-build/argeo.bnd, \ diff --git a/cnf/ext/osgirepo.bnd b/cnf/ext/osgirepo.bnd deleted file mode 100644 index 427e7c558..000000000 --- a/cnf/ext/osgirepo.bnd +++ /dev/null @@ -1,12 +0,0 @@ --plugin.osgirepo=aQute.bnd.repository.osgi.OSGiRepository;\ - locations=file://${workspace}/sdk/target/a2/index.xml;\ - max.stale=-1;\ - poll.time=86400;\ - name=local;\ - cache=${build}/cache/local,\ - aQute.bnd.repository.osgi.OSGiRepository;\ - locations=file://${workspace}/sdk/target/sdk-2.3.1-SNAPSHOT-a2-target/index.xml;\ - max.stale=-1;\ - poll.time=86400;\ - name=local;\ - cache=${build}/cache/local \ No newline at end of file diff --git a/cnf/testing.bnd b/cnf/testing.bnd deleted file mode 100644 index 18e87d642..000000000 --- a/cnf/testing.bnd +++ /dev/null @@ -1,10 +0,0 @@ -MAJOR=2 -MINOR=1 -MICRO=106 -qualifier=.next - -category=org.argeo.commons -Bundle-RequiredExecutionEnvironment=JavaSE-11 - -argeo.rpm.stagingRepository=/srv/rpmfactory/testing/argeo-osgi-2/argeo -argeo.rpm.suffix= diff --git a/cnf/unstable.bnd b/cnf/unstable.bnd deleted file mode 100644 index 6ca2e760e..000000000 --- a/cnf/unstable.bnd +++ /dev/null @@ -1,10 +0,0 @@ -MAJOR=2 -MINOR=3 -MICRO=7 -qualifier=.next - -category=org.argeo.commons -Bundle-RequiredExecutionEnvironment=JavaSE-11 - -argeo.rpm.stagingRepository=/srv/rpmfactory/unstable/argeo-osgi-2/argeo -argeo.rpm.suffix=-unstable diff --git a/configure b/configure old mode 100644 new mode 100755 index 9b3e9804b..47f7d9625 --- a/configure +++ b/configure @@ -1,55 +1,7 @@ #!/bin/sh -# We build where we are -SDK_BUILD_BASE=$(pwd -P)/output - # Source are located where this script is SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)" -SDK_MK=$SDK_SRC_BASE/sdk.mk - -#echo SDK_BUILD_BASE=$SDK_BUILD_BASE -#echo SDK_SRC_BASE=$SDK_SRC_BASE -#echo SDK_MK=$SDK_MK - -if [ -f "$SDK_MK" ]; -then - -echo "File $SDK_MK already exists. Remove it in order to configure a new build location:" -echo "rm $SDK_MK" -exit 1 - -else - -if [ -z "$JAVA_HOME" ] -then -echo "Environment variable JAVA_HOME must be set" -exit 1 -fi - -# Create build directory, so that it can be used right away -# and we check whether we have the rights -mkdir -p $SDK_BUILD_BASE -if [ -f "$SDK_MK" ]; -then -echo "Cannot create $SDK_BUILD_BASE, SDK configuration has failed." -exit 2 -fi - -# Generate sdk.mk -cat > "$SDK_MK" < - - - - - - diff --git a/eclipse/org.argeo.cms.e4/.project b/eclipse/org.argeo.cms.e4/.project deleted file mode 100644 index 0c0406952..000000000 --- a/eclipse/org.argeo.cms.e4/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.e4 - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/eclipse/org.argeo.cms.e4/META-INF/.gitignore b/eclipse/org.argeo.cms.e4/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/eclipse/org.argeo.cms.e4/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml deleted file mode 100644 index fcd3ae5cb..000000000 --- a/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml deleted file mode 100644 index 65690f262..000000000 --- a/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml deleted file mode 100644 index a267aa519..000000000 --- a/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/eclipse/org.argeo.cms.e4/bnd.bnd b/eclipse/org.argeo.cms.e4/bnd.bnd deleted file mode 100644 index ea9504384..000000000 --- a/eclipse/org.argeo.cms.e4/bnd.bnd +++ /dev/null @@ -1,17 +0,0 @@ -Service-Component: OSGI-INF/homeRepository.xml,\ -OSGI-INF/userAdminWrapper.xml,\ -OSGI-INF/defaultCallbackHandler.xml -Bundle-ActivationPolicy: lazy - -Import-Package: org.eclipse.swt,\ -org.eclipse.swt.widgets;version="0.0.0",\ -org.eclipse.e4.ui.model.application.ui,\ -org.eclipse.e4.ui.model.application,\ -javax.jcr.nodetype,\ -org.argeo.cms,\ -org.eclipse.core.commands.common,\ -org.eclipse.jface.window,\ -org.argeo.cms.swt.auth,\ -org.apache.jackrabbit.*;version="[2,3)",\ -javax.servlet.*;version="[3,5)",\ -* diff --git a/eclipse/org.argeo.cms.e4/build.properties b/eclipse/org.argeo.cms.e4/build.properties deleted file mode 100644 index e46a7baee..000000000 --- a/eclipse/org.argeo.cms.e4/build.properties +++ /dev/null @@ -1,9 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - OSGI-INF/,\ - .,\ - OSGI-INF/homeRepository.xml,\ - OSGI-INF/userAdminWrapper.xml,\ - OSGI-INF/defaultCallbackHandler.xml,\ - e4xmi/cms-demo.e4xmi -source.. = src/ diff --git a/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi deleted file mode 100644 index 89bcc376d..000000000 --- a/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi +++ /dev/null @@ -1,129 +0,0 @@ - - - - - shellMaximized - auth.cn=admin,ou=roles,ou=node - - - auth.cn=admin,ou=roles,ou=node - - - - - - - - - - - - - usersEditorArea - - - - - - - - - - - - - - - - - - - - - - - - - ViewMenu - - - - - - - - - - dataExplorer - - - - - - - - - - - - - - - - - - - - - - - - - auth.cn=admin,ou=roles,ou=node - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi deleted file mode 100644 index ef659fcc7..000000000 --- a/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi +++ /dev/null @@ -1,67 +0,0 @@ - - - - - shellMaximized - auth.cn=user,ou=roles,ou=node - - - - - - - - - - - ViewMenu - - - - - - - - - - dataExplorer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java deleted file mode 100644 index 21abf5828..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java +++ /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 null if not found. - */ - public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) { - List cmds = modelService.findElements(app, null, MCommand.class, null); - for (MCommand cmd : cmds) { - if (cmd.getElementId().equals(commandId)) { - return cmd; - } - } - return null; - } - - /** Dynamically creates a direct menu item from a class. */ - public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class clss, String label) { - MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); - dynamicItem.setLabel(label); - Bundle bundle = FrameworkUtil.getBundle(clss); - dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName()); - return dynamicItem; - } - - /** Singleton. */ - private CmsE4Utils() { - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java deleted file mode 100644 index c42a02a14..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.e4; - -import org.argeo.cms.CmsException; -import org.eclipse.e4.core.contexts.ContextFunction; -import org.eclipse.e4.core.contexts.IEclipseContext; -import org.eclipse.e4.core.di.IInjector; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */ -public class OsgiFilterContextFunction extends ContextFunction { - - private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext(); - - @Override - public Object compute(IEclipseContext context, String contextKey) { - ServiceReference[] srs; - try { - srs = bc.getServiceReferences((String) null, contextKey); - } catch (InvalidSyntaxException e) { - throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e); - } - if (srs == null || srs.length == 0) { - return IInjector.NOT_A_VALUE; - } else { - // return the first one - return bc.getService(srs[0]); - } - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java deleted file mode 100644 index 89055d2ff..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java +++ /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 privilegedAction = new PrivilegedAction() { - public IStatus run() { - return doRun(progressMonitor); - } - }; - return Subject.doAs(subject, privilegedAction); - } - - /** - * Implement here what should be executed with default context - * authentication - */ - protected abstract IStatus doRun(IProgressMonitor progressMonitor); -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java deleted file mode 100644 index 3d57e1659..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.argeo.cms.e4.addons; - -import java.security.AccessController; -import java.util.Iterator; - -import javax.annotation.PostConstruct; -import javax.security.auth.Subject; -import javax.servlet.http.HttpServletRequest; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.eclipse.e4.ui.model.application.MApplication; -import org.eclipse.e4.ui.model.application.ui.MElementContainer; -import org.eclipse.e4.ui.model.application.ui.MUIElement; -import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; -import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; -import org.eclipse.e4.ui.model.application.ui.basic.MWindow; - -public class AuthAddon { - private final static CmsLog log = CmsLog.getLog(AuthAddon.class); - - public final static String AUTH = "auth."; - - @PostConstruct - void init(MApplication application) { - Iterator 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 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 container = (MElementContainer) element; - Iterator children = container.getChildren().iterator(); - while (children.hasNext()) { - MUIElement child = children.next(); - boolean visible = process(child); - if (!visible) - children.remove(); - } - - for (Object child : container.getChildren()) { - if (child instanceof MUIElement) { - boolean visible = process((MUIElement) child); - if (!visible) - container.getChildren().remove(child); - } - } - } - - return true; - } - - protected void logout() { - Subject subject = Subject.getSubject(AccessController.getContext()); - try { - CurrentUser.logoutCmsSession(subject); - } catch (Exception e) { - throw new CmsException("Cannot log out", e); - } - HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest(); - if (request != null) - request.getSession().setMaxInactiveInterval(0); - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java deleted file mode 100644 index 5bc0d6936..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java +++ /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 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 windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE, - new ElementMatcher(null, null, (String) null)); - for (MWindow window : windows) { - String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE); - int style = 0; - if (currentStyle != null) { - style = Integer.parseInt(currentStyle); - } - style = style | SWT.RIGHT_TO_LEFT; - window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style)); - } - } - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java deleted file mode 100644 index 6367b42d5..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Eclipse 4 addons to integrate with Argeo CMS. */ -package org.argeo.cms.e4.addons; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java deleted file mode 100644 index cb9f9b97a..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.argeo.cms.e4.files; - -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.spi.FileSystemProvider; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.eclipse.ui.fs.AdvancedFsBrowser; -import org.argeo.eclipse.ui.fs.SimpleFsBrowser; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; - -/** Browse the node file system. */ -public class NodeFsBrowserView { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".nodeFsBrowserView"; - - @Inject - FileSystemProvider nodeFileSystemProvider; - - @PostConstruct - public void createPartControl(Composite parent) { - try { - //URI uri = new URI("node://root:demo@localhost:7070/"); - URI uri = new URI("node:///"); - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) - fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); - Path nodePath = fileSystem.getPath("/"); - - Path localPath = Paths.get(System.getProperty("user.home")); - - SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS); - browser.setInput(nodePath, localPath); -// AdvancedFsBrowser browser = new AdvancedFsBrowser(); -// browser.createUi(parent, localPath); - } catch (Exception e) { - throw new CmsException("Cannot open file system browser", e); - } - } - - public void setFocus() { - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java deleted file mode 100644 index b481dd48a..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Files browser perspective. */ -package org.argeo.cms.e4.files; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java deleted file mode 100644 index 416df7df1..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import java.util.Locale; - -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.core.services.nls.ILocaleChangeService; - -public class ChangeLanguage { - @Execute - public void execute(ILocaleChangeService localeChangeService) { - localeChangeService.changeApplicationLocale(Locale.FRENCH); - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java deleted file mode 100644 index 0ecd0a155..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import static org.argeo.cms.CmsMsg.changePassword; -import static org.argeo.cms.CmsMsg.currentPassword; -import static org.argeo.cms.CmsMsg.newPassword; -import static org.argeo.cms.CmsMsg.passwordChanged; -import static org.argeo.cms.CmsMsg.repeatNewPassword; - -import java.util.Arrays; - -import javax.inject.Inject; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.swt.dialogs.CmsMessageDialog; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.osgi.transaction.WorkTransaction; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.core.di.annotations.Optional; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** Change the password of the logged-in user. */ -public class ChangePassword { - @Inject - private UserAdmin userAdmin; - @Inject - private WorkTransaction userTransaction; - @Inject - @Optional - private CryptoKeyring keyring = null; - - @Execute - public void execute() { - ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin); - if (dialog.open() == Dialog.OK) { - new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(), - CmsMessageDialog.INFORMATION).open(); - } - } - - protected void changePassword(char[] oldPassword, char[] newPassword) { - String name = CurrentUser.getUsername(); - LdapName dn; - try { - dn = new LdapName(name); - } catch (InvalidNameException e) { - throw new CmsException("Invalid user dn " + name, e); - } - User user = (User) userAdmin.getRole(dn.toString()); - if (!user.hasCredential(null, oldPassword)) - throw new CmsException("Invalid password"); - if (Arrays.equals(newPassword, new char[0])) - throw new CmsException("New password empty"); - try { - userTransaction.begin(); - user.getCredentials().put(null, newPassword); - if (keyring != null) { - keyring.changePassword(oldPassword, newPassword); - // TODO change secret keys in the CMS session - } - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - e1.printStackTrace(); - } - if (e instanceof RuntimeException) - throw (RuntimeException) e; - else - throw new CmsException("Cannot change password", e); - } - } - - class ChangePasswordDialog extends CmsMessageDialog { - private Text oldPassword, newPassword1, newPassword2; - - public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) { - super(parentShell, changePassword.lead(), CONFIRM); - } - -// protected Point getInitialSize() { -// return new Point(400, 450); -// } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - oldPassword = createLP(composite, currentPassword.lead()); - newPassword1 = createLP(composite, newPassword.lead()); - newPassword2 = createLP(composite, repeatNewPassword.lead()); - -// parent.pack(); - oldPassword.setFocus(); - return composite; - } - - @Override - protected void okPressed() { - try { - if (!newPassword1.getText().equals(newPassword2.getText())) - throw new CmsException("New passwords are different"); - changePassword(oldPassword.getTextChars(), newPassword1.getTextChars()); - closeShell(OK); - } catch (Exception e) { - ErrorFeedback.show("Cannot change password", e); - } - } - - /** Creates label and password. */ - protected Text createLP(Composite parent, String label) { - new Label(parent, SWT.NONE).setText(label); - Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - return text; - } - - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java deleted file mode 100644 index d11c0412c..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class CloseAllParts { - - @Execute - void execute(EPartService partService) { - for (MPart part : partService.getParts()) { - if (part.isCloseable()) { - if (part.isDirty()) { - if (partService.savePart(part, true)) { - partService.hidePart(part, true); - } - } else { - partService.hidePart(part, true); - } - } - } - } - - @CanExecute - boolean canExecute(EPartService partService) { - boolean atLeastOnePart = false; - for (MPart part : partService.getParts()) { - if (part.isVisible() && part.isCloseable()) { - atLeastOnePart = true; - break; - } - } - return atLeastOnePart; - } - -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java deleted file mode 100644 index a365f3d7d..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import java.security.AccessController; - -import javax.security.auth.Subject; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.workbench.IWorkbench; - -public class CloseWorkbench { - @Execute - public void execute(IWorkbench workbench) { - logout(); - workbench.close(); - } - - protected void logout() { - Subject subject = Subject.getSubject(AccessController.getContext()); - try { - CurrentUser.logoutCmsSession(subject); - } catch (Exception e) { - throw new CmsException("Cannot log out", e); - } - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java deleted file mode 100644 index 358494c5b..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.Execute; - -public class DoNothing { - @Execute - public void execute() { - - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java deleted file mode 100644 index ac825bb0d..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java +++ /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 items, EModelService modelService) { - MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); - dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")"); - //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4"); - //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName()); - dynamicItem.setEnabled(true); - dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage"); - items.add(dynamicItem); - } - - @AboutToHide - public void aboutToHide() { - - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java deleted file mode 100644 index ac544b107..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java +++ /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 perspectives = modelService.findElements(application, perspectiveId, MPerspective.class, - null); - if (perspectives.size() == 0) - return; - MPerspective perspective = perspectives.get(0); - partService.switchPerspective(perspective); - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java deleted file mode 100644 index 3b60abd7e..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class SaveAllParts { - - @Execute - void execute(EPartService partService) { - partService.saveAll(false); - } - - @CanExecute - boolean canExecute(EPartService partService) { - return partService.getDirtyParts().size() > 0; - } - -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java deleted file mode 100644 index 73486f363..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class SavePart { - @Execute - void execute(EPartService partService, MPart part) { - partService.savePart(part, false); - } - - @CanExecute - boolean canExecute(MPart part) { - return part.isDirty(); - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java deleted file mode 100644 index a44ca9056..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Eclipse 4 handlers. */ -package org.argeo.cms.e4.handlers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java deleted file mode 100644 index e17f17bb7..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java +++ /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 propList = new ArrayList(); - while (pi.hasNext()) { - propList.add(pi.nextProperty()); - } - props = propList.toArray(); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected exception while listing node properties", e); - } - return props; - } - - public Object getParent(Object child) { - return null; - } - - public Object[] getChildren(Object parent) { - if (parent instanceof Property) { - Property prop = (Property) parent; - try { - if (prop.isMultiple()) - return prop.getValues(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get multi-prop values on " + prop, e); - } - } - return null; - } - - public boolean hasChildren(Object parent) { - try { - return (parent instanceof Property && ((Property) parent).isMultiple()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check if property is multiple for " + parent, e); - } - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - public void dispose() { - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java deleted file mode 100644 index 98e80936d..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java +++ /dev/null @@ -1,349 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventListener; -import javax.jcr.observation.ObservationManager; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.security.Keyring; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.jcr.JcrBrowserUtils; -import org.argeo.cms.ui.jcr.NodeContentProvider; -import org.argeo.cms.ui.jcr.NodeLabelProvider; -import org.argeo.cms.ui.jcr.OsgiRepositoryRegister; -import org.argeo.cms.ui.jcr.PropertiesContentProvider; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.jcr.AsyncUiEventListener; -import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer; -import org.argeo.jcr.JcrUtils; -import org.eclipse.e4.core.contexts.IEclipseContext; -import org.eclipse.e4.core.di.annotations.Optional; -import org.eclipse.e4.ui.services.EMenuService; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.IBaseLabelProvider; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; - -/** - * Basic View to display a sash form to browse a JCR compliant multiple - * repository environment - */ -public class JcrBrowserView { - final static String ID = "org.argeo.cms.e4.jcrbrowser"; - final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer"; - - private boolean sortChildNodes = true; - - /* DEPENDENCY INJECTION */ - @Inject - @Optional - private Keyring keyring; - @Inject - private RepositoryFactory repositoryFactory; - @Inject - private Repository nodeRepository; - - // Current user session on the home repository default workspace - private Session userSession; - - private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister(); - - // This page widgets - private TreeViewer nodesViewer; - private NodeContentProvider nodeContentProvider; - private TableViewer propertiesViewer; - private EventListener resultsObserver; - - @PostConstruct - public void createPartControl(Composite parent, IEclipseContext context, EPartService partService, - ESelectionService selectionService, EMenuService menuService) { - repositoryRegister.init(); - - parent.setLayout(new FillLayout()); - SashForm sashForm = new SashForm(parent, SWT.VERTICAL); - // sashForm.setSashWidth(4); - // sashForm.setLayout(new FillLayout()); - - // Create the tree on top of the view - Composite top = new Composite(sashForm, SWT.NONE); - // GridLayout gl = new GridLayout(1, false); - top.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - try { - this.userSession = this.nodeRepository.login(CmsConstants.HOME_WORKSPACE); - } catch (RepositoryException e) { - throw new CmsException("Cannot open user session", e); - } - - nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory, - sortChildNodes); - - // nodes viewer - nodesViewer = createNodeViewer(top, nodeContentProvider); - - // context menu : it is completely defined in the plugin.xml file. - // MenuManager menuManager = new MenuManager(); - // Menu menu = menuManager.createContextMenu(nodesViewer.getTree()); - - // nodesViewer.getTree().setMenu(menu); - - nodesViewer.setInput(""); - - // Create the property viewer on the bottom - Composite bottom = new Composite(sashForm, SWT.NONE); - bottom.setLayout(CmsSwtUtils.noSpaceGridLayout()); - propertiesViewer = createPropertiesViewer(bottom); - - sashForm.setWeights(getWeights()); - nodesViewer.setComparer(new NodeViewerComparer()); - nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) event.getSelection(); - selectionService.setSelection(selection.toList()); - } - }); - nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService)); - menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID); - // getSite().registerContextMenu(menuManager, nodesViewer); - // getSite().setSelectionProvider(nodesViewer); - } - - @PreDestroy - public void dispose() { - JcrUtils.logoutQuietly(userSession); - repositoryRegister.destroy(); - } - - public void refresh(Object obj) { - // Enable full refresh from a command when no element of the tree is - // selected - if (obj == null) { - Object[] elements = nodeContentProvider.getElements(null); - for (Object el : elements) { - if (el instanceof TreeParent) - JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el); - getNodeViewer().refresh(el); - } - } else - getNodeViewer().refresh(obj); - } - - /** - * To be overridden to adapt size of form and result frames. - */ - protected int[] getWeights() { - return new int[] { 70, 30 }; - } - - protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) { - - final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI); - - tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - tmpNodeViewer.setContentProvider(nodeContentProvider); - tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider()); - tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - if (!event.getSelection().isEmpty()) { - IStructuredSelection sel = (IStructuredSelection) event.getSelection(); - Object firstItem = sel.getFirstElement(); - if (firstItem instanceof SingleJcrNodeElem) - propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode()); - } else { - propertiesViewer.setInput(""); - } - } - }); - - resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay()); - if (keyring != null) - try { - ObservationManager observationManager = userSession.getWorkspace().getObservationManager(); - observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/", - true, null, null, false); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot register listeners", e); - } - - // tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer)); - return tmpNodeViewer; - } - - protected TableViewer createPropertiesViewer(Composite parent) { - propertiesViewer = new TableViewer(parent, SWT.NONE); - propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - propertiesViewer.getTable().setHeaderVisible(true); - propertiesViewer.setContentProvider(new PropertiesContentProvider()); - TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE); - col.getColumn().setText("Name"); - col.getColumn().setWidth(200); - col.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -6684361063107478595L; - - public String getText(Object element) { - try { - return ((Property) element).getName(); - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected exception in label provider", e); - } - } - }); - col = new TableViewerColumn(propertiesViewer, SWT.NONE); - col.getColumn().setText("Value"); - col.getColumn().setWidth(400); - col.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -8201994187693336657L; - - public String getText(Object element) { - try { - Property property = (Property) element; - if (property.getType() == PropertyType.BINARY) - return ""; - 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 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 events) throws RepositoryException { - if (getLog().isTraceEnabled()) - getLog().trace("Refresh result list"); - nodesViewer.refresh(); - } - - } - - public boolean getSortChildNodes() { - return sortChildNodes; - } - - public void setFocus() { - getNodeViewer().getTree().setFocus(); - } - - /* DEPENDENCY INJECTION */ - // public void setRepositoryRegister(RepositoryRegister repositoryRegister) { - // this.repositoryRegister = repositoryRegister; - // } - - public void setKeyring(CryptoKeyring keyring) { - this.keyring = keyring; - } - - public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - this.repositoryFactory = repositoryFactory; - } - - public void setNodeRepository(Repository nodeRepository) { - this.nodeRepository = nodeRepository; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java deleted file mode 100644 index ad6a547da..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.jcr.JcrDClickListener; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; -import org.eclipse.jface.viewers.TreeViewer; - -public class JcrE4DClickListener extends JcrDClickListener { - EPartService partService; - - public JcrE4DClickListener(TreeViewer nodeViewer, EPartService partService) { - super(nodeViewer); - this.partService = partService; - } - - @Override - protected void openNode(Node node) { - MPart part = partService.createPart(JcrNodeEditor.DESCRIPTOR_ID); - try { - part.setLabel(node.getName()); - part.getPersistedState().put("nodeWorkspace", node.getSession().getWorkspace().getName()); - part.getPersistedState().put("nodePath", node.getPath()); - } catch (RepositoryException e) { - throw new CmsException("Cannot open " + node, e); - } - - // the provided part is be shown - partService.showPart(part, PartState.ACTIVATE); - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java deleted file mode 100644 index ae2b325f5..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.jcr.Node; - -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; - -public class JcrNodeEditor { - final static String DESCRIPTOR_ID = "org.argeo.cms.e4.partdescriptor.nodeEditor"; - - @PostConstruct - public void createUi(Composite parent, MPart part, ESelectionService selectionService) { - parent.setLayout(new FillLayout()); - List selection = (List) selectionService.getSelection(); - Node node = ((SingleJcrNodeElem) selection.get(0)).getNode(); - GenericPropertyPage propertyPage = new GenericPropertyPage(node); - propertyPage.createFormContent(parent); - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java deleted file mode 100644 index 17d8d2a23..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import javax.annotation.PostConstruct; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; - -public class SimplePart { - - @PostConstruct - void init(Composite parent) { - parent.setLayout(new GridLayout()); - Label label = new Label(parent, SWT.NONE); - label.setText("Hello e4 World"); - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java deleted file mode 100644 index 8f5bc36e1..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.eclipse.ui.dialogs.SingleValue; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; - -/** - * Adds a node of type nt:folder, only on {@link SingleJcrNodeElem} and - * {@link WorkspaceElem} TreeObject types. - * - * This handler assumes that a selection provider is available and picks only - * first selected item. It is UI's job to enable the command only when the - * selection contains one and only one element. Thus no parameter is passed - * through the command. - */ -public class AddFolderNode { - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - List selection = (List) selectionService.getSelection(); - JcrBrowserView view = (JcrBrowserView) part.getObject(); - - if (selection != null && selection.size() == 1) { - TreeParent treeParentNode = null; - Node jcrParentNode = null; - Object obj = selection.get(0); - - if (obj instanceof SingleJcrNodeElem) { - treeParentNode = (TreeParent) obj; - jcrParentNode = ((SingleJcrNodeElem) treeParentNode).getNode(); - } else if (obj instanceof WorkspaceElem) { - treeParentNode = (TreeParent) obj; - jcrParentNode = ((WorkspaceElem) treeParentNode).getRootNode(); - } else - return; - - String folderName = SingleValue.ask("Folder name", "Enter folder name"); - if (folderName != null) { - try { - jcrParentNode.addNode(folderName, NodeType.NT_FOLDER); - jcrParentNode.getSession().save(); - view.nodeAdded(treeParentNode); - } catch (RepositoryException e) { - ErrorFeedback.show("Cannot create folder " + folderName + " under " + treeParentNode, e); - } - } - } else { - // ErrorFeedback.show(WorkbenchUiPlugin - // .getMessage("errorUnvalidNtFolderNodeType")); - ErrorFeedback.show("Invalid NT folder node type"); - } - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java deleted file mode 100644 index dc47f6edf..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java +++ /dev/null @@ -1,210 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.net.URI; -import java.util.Hashtable; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.jcr.JcrUtils; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.core.di.annotations.Optional; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Connect to a remote repository and, if successful publish it as an OSGi - * service. - */ -public class AddRemoteRepository { - - @Inject - private RepositoryFactory repositoryFactory; - @Inject - private Repository nodeRepository; - @Inject - @Optional - private Keyring keyring; - - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part) { - JcrBrowserView view = (JcrBrowserView) part.getObject(); - RemoteRepositoryLoginDialog dlg = new RemoteRepositoryLoginDialog(Display.getDefault().getActiveShell()); - if (dlg.open() == Dialog.OK) { - view.refresh(null); - } - } - - // public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - // this.repositoryFactory = repositoryFactory; - // } - // - // public void setKeyring(Keyring keyring) { - // this.keyring = keyring; - // } - // - // public void setNodeRepository(Repository nodeRepository) { - // this.nodeRepository = nodeRepository; - // } - - class RemoteRepositoryLoginDialog extends TitleAreaDialog { - private static final long serialVersionUID = 2234006887750103399L; - private Text name; - private Text uri; - private Text username; - private Text password; - private Button saveInKeyring; - - public RemoteRepositoryLoginDialog(Shell parentShell) { - super(parentShell); - } - - protected Point getInitialSize() { - return new Point(600, 400); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - setMessage("Login to remote repository", IMessageProvider.NONE); - name = createLT(composite, "Name", "remoteRepository"); - uri = createLT(composite, "URI", "http://localhost:7070/jcr/node"); - username = createLT(composite, "User", ""); - password = createLP(composite, "Password"); - - saveInKeyring = createLC(composite, "Remember password", false); - parent.pack(); - return composite; - } - - @Override - protected void createButtonsForButtonBar(Composite parent) { - super.createButtonsForButtonBar(parent); - Button test = createButton(parent, 2, "Test", false); - test.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -1829962269440419560L; - - public void widgetSelected(SelectionEvent arg0) { - testConnection(); - } - }); - } - - void testConnection() { - Session session = null; - try { - URI checkedUri = new URI(uri.getText()); - String checkedUriStr = checkedUri.toString(); - - Hashtable params = new Hashtable(); - params.put(CmsConstants.LABELED_URI, checkedUriStr); - Repository repository = repositoryFactory.getRepository(params); - if (username.getText().trim().equals("")) {// anonymous - // FIXME make it more generic - session = repository.login(CmsConstants.SYS_WORKSPACE); - } else { - // FIXME use getTextChars() when upgrading to 3.7 - // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=297412 - char[] pwd = password.getText().toCharArray(); - SimpleCredentials sc = new SimpleCredentials(username.getText(), pwd); - session = repository.login(sc, "main"); - MessageDialog.openInformation(getParentShell(), "Success", - "Connection to '" + uri.getText() + "' successful"); - } - } catch (Exception e) { - ErrorFeedback.show("Connection test failed for " + uri.getText(), e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - @Override - protected void okPressed() { - Session nodeSession = null; - try { - nodeSession = nodeRepository.login(); - Node home = CmsJcrUtils.getUserHome(nodeSession); - - Node remote = home.hasNode(ArgeoNames.ARGEO_REMOTE) ? home.getNode(ArgeoNames.ARGEO_REMOTE) - : home.addNode(ArgeoNames.ARGEO_REMOTE); - if (remote.hasNode(name.getText())) - throw new EclipseUiException("There is already a remote repository named " + name.getText()); - Node remoteRepository = remote.addNode(name.getText(), ArgeoTypes.ARGEO_REMOTE_REPOSITORY); - remoteRepository.setProperty(ArgeoNames.ARGEO_URI, uri.getText()); - remoteRepository.setProperty(ArgeoNames.ARGEO_USER_ID, username.getText()); - nodeSession.save(); - if (saveInKeyring.getSelection()) { - String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; - keyring.set(pwdPath, password.getText().toCharArray()); - } - nodeSession.save(); - MessageDialog.openInformation(getParentShell(), "Repository Added", - "Remote repository '" + username.getText() + "@" + uri.getText() + "' added"); - - super.okPressed(); - } catch (Exception e) { - ErrorFeedback.show("Cannot add remote repository", e); - } finally { - JcrUtils.logoutQuietly(nodeSession); - } - } - - /** Creates label and text. */ - protected Text createLT(Composite parent, String label, String initial) { - new Label(parent, SWT.NONE).setText(label); - Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.setText(initial); - return text; - } - - /** Creates label and check. */ - protected Button createLC(Composite parent, String label, Boolean initial) { - new Label(parent, SWT.NONE).setText(label); - Button check = new Button(parent, SWT.CHECK); - check.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - check.setSelection(initial); - return check; - } - - protected Text createLP(Composite parent, String label) { - new Label(parent, SWT.NONE).setText(label); - Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER | SWT.PASSWORD); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return text; - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java deleted file mode 100644 index 1974e4d44..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.widgets.Display; - -/** - * Delete the selected nodes: both in the JCR repository and in the UI view. - * Warning no check is done, except implementation dependent native checks, - * handle with care. - * - * This handler is still 'hard linked' to a GenericJcrBrowser view to enable - * correct tree refresh when a node is added. This must be corrected in future - * versions. - */ -public class DeleteNodes { - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - List selection = (List) selectionService.getSelection(); - if (selection == null) - return; - - JcrBrowserView view = (JcrBrowserView) part.getObject(); - - // confirmation - StringBuffer buf = new StringBuffer(""); - for (Object o : selection) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) o; - buf.append(sjn.getName()).append(' '); - } - Boolean doRemove = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Confirm deletion", - "Do you want to delete " + buf + "?"); - - // operation - if (doRemove) { - SingleJcrNodeElem ancestor = null; - WorkspaceElem rootAncestor = null; - try { - for (Object obj : selection) { - if (obj instanceof SingleJcrNodeElem) { - // Cache objects - SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; - TreeParent tp = (TreeParent) sjn.getParent(); - Node node = sjn.getNode(); - - // Jcr Remove - node.remove(); - node.getSession().save(); - // UI remove - tp.removeChild(sjn); - - // Check if the parent is the root node - if (tp instanceof WorkspaceElem) - rootAncestor = (WorkspaceElem) tp; - else - ancestor = getOlder(ancestor, (SingleJcrNodeElem) tp); - } - } - if (rootAncestor != null) - view.nodeRemoved(rootAncestor); - else if (ancestor != null) - view.nodeRemoved(ancestor); - } catch (Exception e) { - ErrorFeedback.show("Cannot delete selected node ", e); - } - } - } - - private SingleJcrNodeElem getOlder(SingleJcrNodeElem A, SingleJcrNodeElem B) { - try { - if (A == null) - return B == null ? null : B; - // Todo enhanced this method - else - return A.getNode().getDepth() <= B.getNode().getDepth() ? A : B; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot find ancestor", re); - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java deleted file mode 100644 index 4ae072cb5..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java +++ /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: - *
    - *
  1. The model objects are recomputed
  2. - *
  3. the view is refreshed
  4. - *
- */ -public class Refresh { - - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, - ESelectionService selectionService) { - - JcrBrowserView view = (JcrBrowserView) part.getObject(); - List selection = (List) selectionService.getSelection(); - - if (selection != null && !selection.isEmpty()) { - for (Object obj : selection) - if (obj instanceof TreeParent) { - TreeParent tp = (TreeParent) obj; - JcrBrowserUtils.forceRefreshIfNeeded(tp); - view.refresh(obj); - } - } else if (view instanceof JcrBrowserView) - view.refresh(null); // force full refresh - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java deleted file mode 100644 index 97674abc2..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.dialogs.SingleValue; -import org.argeo.jcr.JcrUtils; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; - -/** - * Canonically call JCR Session#move(String, String) on the first element - * returned by HandlerUtil#getActiveWorkbenchWindow() - * (...getActivePage().getSelection()), if it is a {@link SingleJcrNodeElem}. - * The user must then fill a new name in and confirm - */ -public class RenameNode { - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, - ESelectionService selectionService) { - List selection = (List) selectionService.getSelection(); - if (selection == null || selection.size() != 1) - return; - JcrBrowserView view = (JcrBrowserView) part.getObject(); - - Object element = selection.get(0); - if (element instanceof SingleJcrNodeElem) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; - Node node = sjn.getNode(); - Session session = null; - String newName = null; - String oldPath = null; - try { - newName = SingleValue.ask("New node name", "Please provide a new name for [" + node.getName() + "]"); - // TODO sanity check and user feedback - newName = JcrUtils.replaceInvalidChars(newName); - oldPath = node.getPath(); - session = node.getSession(); - session.move(oldPath, JcrUtils.parentPath(oldPath) + "/" + newName); - session.save(); - - // Manually refresh the browser view. Must be enhanced - view.refresh(sjn); - } catch (RepositoryException e) { - throw new EclipseUiException("Unable to rename " + node + " to " + newName, e); - } - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java deleted file mode 100644 index 4e075e2b1..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** JCR browser handlers. */ -package org.argeo.cms.e4.jcr.handlers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java deleted file mode 100644 index 3e92fb04d..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** JCR browser perspective. */ -package org.argeo.cms.e4.jcr; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java deleted file mode 100644 index 4fd1d68dc..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.util.Collection; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -abstract class AbstractOsgiComposite extends Composite { - private static final long serialVersionUID = -4097415973477517137L; - protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - protected final CmsLog log = CmsLog.getLog(getClass()); - - public AbstractOsgiComposite(Composite parent, int style) { - super(parent, style); - parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); - setLayout(CmsSwtUtils.noSpaceGridLayout()); - setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - initUi(style); - } - - protected abstract void initUi(int style); - - protected T getService(Class clazz) { - return bc.getService(bc.getServiceReference(clazz)); - } - - protected Collection> getServiceReferences(Class clazz, String filter) { - try { - return bc.getServiceReferences(clazz, filter); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Filter " + filter + " is invalid", e); - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java deleted file mode 100644 index 260a114cd..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java +++ /dev/null @@ -1,576 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import static org.eclipse.swt.SWT.RIGHT; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.LinkedHashMap; - -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.util.CmsLink; -import org.argeo.cms.ui.widgets.EditableImage; -import org.argeo.cms.ui.widgets.Img; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ILazyContentProvider; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.Text; - -public class Browse implements CmsUiProvider { - - // Some local constants to experiment. should be cleaned - private final static String BROWSE_PREFIX = "browse#"; - private final static int THUMBNAIL_WIDTH = 400; - private final static int COLUMN_WIDTH = 160; - private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm"); - - // keep a cache of the opened nodes - // Key is the path - private LinkedHashMap browserCols = new LinkedHashMap(); - 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("" + context.getName() + ""); - 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 = ""; - else if (value.getType() == PropertyType.DATE) - result = timeFormatter.format(value.getDate().getTime()); - else - result = value.getString(); - } - return result; - } - - private String getMultiAsString(Property property, String separator) throws RepositoryException { - if (separator == null) - separator = "; "; - Value[] values = property.getValues(); - StringBuilder builder = new StringBuilder(); - for (Value val : values) { - String currStr = val.getString(); - if (!"".equals(currStr.trim())) - builder.append(currStr).append(separator); - } - if (builder.lastIndexOf(separator) >= 0) - return builder.substring(0, builder.length() - separator.length()); - else - return builder.toString(); - } - - /** Almost canonical implementation of a table that display entities */ - private class FilterEntitiesVirtualTable extends Composite { - private static final long serialVersionUID = 8798147431706283824L; - - // Context - private Node context; - - // UI Objects - private TableViewer entityViewer; - - // enable management of multiple columns - Node getNode() { - return context; - } - - @Override - public boolean setFocus() { - if (entityViewer.getTable().isDisposed()) - return false; - if (entityViewer.getSelection().isEmpty()) { - Object first = entityViewer.getElementAt(0); - if (first != null) { - entityViewer.setSelection(new StructuredSelection(first), true); - } - } - return entityViewer.getTable().setFocus(); - } - - void filterList(String filter) { - try { - NodeIterator nit = context.getNodes(filter); - refreshFilteredList(nit); - } catch (RepositoryException e) { - throw new CmsException("Unable to filter " + getNode() + " children with filter " + filter, e); - } - - } - - public FilterEntitiesVirtualTable(Composite parent, int style, Node context) { - super(parent, SWT.NO_FOCUS); - this.context = context; - populate(); - } - - protected void populate() { - Composite parent = this; - GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); - - this.setLayout(layout); - createTableViewer(parent); - } - - private void createTableViewer(final Composite parent) { - // the list - // We must limit the size of the table otherwise the full list is - // loaded - // before the layout happens - Composite listCmp = new Composite(parent, SWT.NO_FOCUS); - GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); - gd.widthHint = COLUMN_WIDTH; - listCmp.setLayoutData(gd); - listCmp.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE); - Table table = entityViewer.getTable(); - - table.setLayoutData(CmsSwtUtils.fillAll()); - table.setLinesVisible(true); - table.setHeaderVisible(false); - CmsSwtUtils.markup(table); - - CmsSwtUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); - - // first column - TableViewerColumn column = new TableViewerColumn(entityViewer, SWT.NONE); - TableColumn tcol = column.getColumn(); - tcol.setWidth(COLUMN_WIDTH); - tcol.setResizable(true); - column.setLabelProvider(new SimpleNameLP()); - - entityViewer.setContentProvider(new MyLazyCP(entityViewer)); - entityViewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); - if (selection.isEmpty()) - return; - else - setEdited((Node) selection.getFirstElement()); - - } - }); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -330694313896036230L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - - IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); - Node selected = null; - if (!selection.isEmpty()) - selected = ((Node) selection.getFirstElement()); - try { - if (e.keyCode == SWT.ARROW_RIGHT) { - if (selected != null) { - setEdited(selected); - browserCols.get(selected.getPath()).setFocus(); - } - } else if (e.keyCode == SWT.ARROW_LEFT) { - try { - selected = getNode().getParent(); - String newPath = selected.getPath(); // getNode().getParent() - setEdited(selected); - if (browserCols.containsKey(newPath)) - browserCols.get(newPath).setFocus(); - } catch (ItemNotFoundException ie) { - // root silent - } - } - } catch (RepositoryException ie) { - throw new CmsException("Error while managing arrow " + "events in the browser for " + selected, - ie); - } - } - }); - } - - private class MyLazyCP implements ILazyContentProvider { - private static final long serialVersionUID = 1L; - private TableViewer viewer; - private Object[] elements; - - public MyLazyCP(TableViewer viewer) { - this.viewer = viewer; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // IMPORTANT: don't forget this: an exception will be thrown if - // a selected object is not part of the results anymore. - viewer.setSelection(null); - this.elements = (Object[]) newInput; - } - - public void updateElement(int index) { - viewer.replace(elements[index], index); - } - } - - protected void refreshFilteredList(NodeIterator children) { - Object[] rows = JcrUtils.nodeIteratorToList(children).toArray(); - entityViewer.setInput(rows); - entityViewer.setItemCount(rows.length); - entityViewer.refresh(); - } - - public class SimpleNameLP extends ColumnLabelProvider { - private static final long serialVersionUID = 2465059387875338553L; - - @Override - public String getText(Object element) { - if (element instanceof Node) { - Node curr = ((Node) element); - try { - return curr.getName(); - } catch (RepositoryException e) { - throw new CmsException("Unable to get name for" + curr); - } - } - return super.getText(element); - } - } - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java deleted file mode 100644 index 97f3e6713..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; -import org.osgi.service.useradmin.UserAdmin; - -class ConnectivityDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public ConnectivityDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - StringBuffer text = new StringBuffer(); - text.append("Provided Servers
"); - - ServiceReference 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("http ").append(httpPort).append("
"); - if (httpsPort != null) - text.append("https ").append(httpsPort).append("
"); - - } - - text.append("
"); - text.append("Referenced Servers
"); - - Label label = new Label(this, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsSwtUtils.markup(label); - label.setText(text.toString()); - } - - protected boolean isDeployed() { - return bc.getServiceReference(UserAdmin.class) != null; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java deleted file mode 100644 index ef95bde64..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.ServiceReference; - -class DataDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public DataDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - if (isDeployed()) { - initCurrentUi(this); - } else { - initNewUi(this); - } - } - - private void initNewUi(Composite parent) { -// try { -// ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); -// Configuration[] confs = confAdmin.listConfigurations( -// "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); -// if (confs == null || confs.length == 0) { -// Group buttonGroup = new Group(parent, SWT.NONE); -// buttonGroup.setText("Repository Type"); -// buttonGroup.setLayout(new GridLayout(2, true)); -// buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL)); -// -// SelectionListener selectionListener = new SelectionAdapter() { -// private static final long serialVersionUID = 6247064348421088092L; -// -// public void widgetSelected(SelectionEvent event) { -// Button radio = (Button) event.widget; -// if (!radio.getSelection()) -// return; -// log.debug(event); -// JackrabbitType nodeType = (JackrabbitType) radio.getData(); -// if (log.isDebugEnabled()) -// log.debug(" selected = " + nodeType.name()); -// }; -// }; -// -// for (JackrabbitType nodeType : JackrabbitType.values()) { -// Button radio = new Button(buttonGroup, SWT.RADIO); -// radio.setText(nodeType.name()); -// radio.setData(nodeType); -// if (nodeType.equals(JackrabbitType.localfs)) -// radio.setSelection(true); -// radio.addSelectionListener(selectionListener); -// } -// -// } else if (confs.length == 1) { -// -// } else { -// throw new CmsException("Multiple repos not yet supported"); -// } -// } catch (Exception e) { -// throw new CmsException("Cannot initialize UI", e); -// } - - } - - private void initCurrentUi(Composite parent) { - parent.setLayout(new GridLayout()); - Collection> contexts = getServiceReferences(RepositoryContext.class, - "(" + CmsConstants.CN + "=*)"); - StringBuffer text = new StringBuffer(); - text.append("Jackrabbit Repositories
"); - for (ServiceReference 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("" + alias + "
"); - text.append("rootNodeId: " + rootNodeId + "
"); - try { - FileStore fileStore = Files.getFileStore(repoHomePath); - text.append("partition: " + fileStore.toString() + "
"); - text.append( - percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)
"); - } 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("" + text.toString() + ""); - } - - 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 = ""; - else if (percent < 95) - span = ""; - else - span = ""; - return span + percent + "%"; - } - - protected boolean isDeployed() { - return bc.getServiceReference(RepositoryContext.class) != null; - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java deleted file mode 100644 index e713f53e1..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsState; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; - -class DeploymentEntryPoint { - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - protected void createContents(Composite parent) { - // FIXME manage authentication if needed - // if (!CurrentUser.roles().contains(AuthConstants.ROLE_ADMIN)) - // return; - - // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - if (isDesktop()) { - parent.setLayout(new GridLayout(2, true)); - } else { - // TODO add scrolling - parent.setLayout(new GridLayout(1, true)); - } - - initHighLevelSummary(parent); - - Group securityGroup = createHighLevelGroup(parent, "Security"); - securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - new SecurityDeploymentUi(securityGroup, SWT.NONE); - - Group dataGroup = createHighLevelGroup(parent, "Data"); - dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - new DataDeploymentUi(dataGroup, SWT.NONE); - - Group logGroup = createHighLevelGroup(parent, "Notifications"); - logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); - new LogDeploymentUi(logGroup, SWT.NONE); - - Group connectivityGroup = createHighLevelGroup(parent, "Connectivity"); - new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE); - connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); - - } - - private void initHighLevelSummary(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); - if (isDesktop()) - gridData.horizontalSpan = 3; - composite.setLayoutData(gridData); - composite.setLayout(new FillLayout()); - - ServiceReference nodeStateRef = bc.getServiceReference(CmsState.class); - if (nodeStateRef == null) - throw new IllegalStateException("No CMS state available"); - CmsState nodeState = bc.getService(nodeStateRef); - ServiceReference nodeDeploymentRef = bc.getServiceReference(CmsContext.class); - Label label = new Label(composite, SWT.WRAP); - CmsSwtUtils.markup(label); - if (nodeDeploymentRef == null) { - label.setText("Not yet deployed on
" + nodeState.getHostname() + "
, 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("[" + "" + nodeState.getHostname() + "]# " + "Deployment state " + stateUuid - + ", available since " + calendar.getTime() + ""); - } - } - - private static Group createHighLevelGroup(Composite parent, String text) { - Group group = new Group(parent, SWT.NONE); - group.setText(text); - CmsSwtUtils.markup(group); - return group; - } - - private boolean isDesktop() { - return true; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java deleted file mode 100644 index fa5d3dae3..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Enumeration; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogListener; -import org.osgi.service.log.LogReaderService; - -class LogDeploymentUi extends AbstractOsgiComposite implements LogListener { - private static final long serialVersionUID = 590221539553514693L; - - private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm"); - - private Display display; - private Text logDisplay; - - public LogDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - LogReaderService logReader = getService(LogReaderService.class); - // FIXME use server push - // logReader.addLogListener(this); - this.display = getDisplay(); - this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); - logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - CmsSwtUtils.markup(logDisplay); - Enumeration logEntries = (Enumeration) logReader.getLog(); - while (logEntries.hasMoreElements()) - logDisplay.append(printEntry(logEntries.nextElement())); - } - - private String printEntry(LogEntry entry) { - StringBuilder sb = new StringBuilder(); - GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault()); - calendar.setTimeInMillis(entry.getTime()); - sb.append(dateFormat.format(calendar.getTime())).append(' '); - sb.append(entry.getMessage()); - sb.append('\n'); - return sb.toString(); - } - - @Override - public void logged(LogEntry entry) { - if (display.isDisposed()) - return; - display.asyncExec(() -> { - if (logDisplay.isDisposed()) - return; - logDisplay.append(printEntry(entry)); - }); - display.wake(); - } - - // @Override - // public void dispose() { - // super.dispose(); - // getService(LogReaderService.class).removeLogListener(this); - // } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java deleted file mode 100644 index df1be51ad..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -/** Specific styles used by the various maintenance pages . */ -public interface MaintenanceStyles { - // General - public final static String PREFIX = "maintenance_"; - - // Browser - public final static String BROWSER_COLUMN = "browser_column"; - } diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java deleted file mode 100644 index cb38ce899..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -public class NonAdminPage implements CmsUiProvider{ - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Composite body = new Composite(parent, SWT.NO_FOCUS); - body.setLayoutData(CmsSwtUtils.fillAll()); - body.setLayout(new GridLayout()); - Label label = new Label(body, SWT.NONE); - label.setText("You should be an admin to perform maintenance operations. " - + "Are you sure you are logged in?"); - label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); - return null; - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java deleted file mode 100644 index 3492c5499..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.net.URI; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -class SecurityDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public SecurityDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - if (isDeployed()) { - initCurrentUi(this); - } else { - initNewUi(this); - } - } - - private void initNewUi(Composite parent) { - new Label(parent, SWT.NONE).setText("Security is not configured"); - } - - private void initCurrentUi(Composite parent) { - ServiceReference userAdminRef = bc.getServiceReference(UserAdmin.class); - UserAdmin userAdmin = bc.getService(userAdminRef); - StringBuffer text = new StringBuffer(); - text.append("Domains
"); - 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(""); - else - text.append(""); - - text.append(rootDn); - text.append("
"); - 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.
"); - } catch (InvalidSyntaxException e) { - log.error("Invalid syntax", e); - } - } - Label label = new Label(parent, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsSwtUtils.markup(label); - label.setText(text.toString()); - } - - protected boolean isDeployed() { - return bc.getServiceReference(UserAdmin.class) != null; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java deleted file mode 100644 index e4d2ad4c3..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Maintenance perspective. */ -package org.argeo.cms.e4.maintenance; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java deleted file mode 100644 index 962ad386e..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.argeo.eclipse.ui.TreeParent; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; - -/** A tree element representing a {@link Bundle} */ -class BundleNode extends TreeParent { - private final Bundle bundle; - - public BundleNode(Bundle bundle) { - this(bundle, false); - } - - @SuppressWarnings("rawtypes") - public BundleNode(Bundle bundle, boolean hasChildren) { - super(bundle.getSymbolicName()); - this.bundle = bundle; - - if (hasChildren) { - // REFERENCES - ServiceReference[] usedServices = bundle.getServicesInUse(); - if (usedServices != null) { - for (ServiceReference sr : usedServices) { - if (sr != null) - addChild(new ServiceReferenceNode(sr, false)); - } - } - - // SERVICES - ServiceReference[] registeredServices = bundle - .getRegisteredServices(); - if (registeredServices != null) { - for (ServiceReference sr : registeredServices) { - if (sr != null) - addChild(new ServiceReferenceNode(sr, true)); - } - } - } - - } - - Bundle getBundle() { - return bundle; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java deleted file mode 100644 index c639255b6..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java +++ /dev/null @@ -1,114 +0,0 @@ -//package org.argeo.eclipse.ui.workbench.osgi; -//public class BundlesView {} - -package org.argeo.cms.e4.monitoring; - -import javax.annotation.PostConstruct; - -import org.argeo.eclipse.ui.ColumnViewerComparator; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** - * Overview of the bundles as a table. Equivalent to Equinox 'ss' console - * command. - */ -public class BundlesView { - private final static BundleContext bc = FrameworkUtil.getBundle(BundlesView.class).getBundleContext(); - private TableViewer viewer; - - @PostConstruct - public void createPartControl(Composite parent) { - viewer = new TableViewer(parent); - viewer.setContentProvider(new BundleContentProvider()); - viewer.getTable().setHeaderVisible(true); - - EclipseUiSpecificUtils.enableToolTipSupport(viewer); - - // ID - TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(30); - column.getColumn().setText("ID"); - column.getColumn().setAlignment(SWT.RIGHT); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -3122136344359358605L; - - public String getText(Object element) { - return Long.toString(((Bundle) element).getBundleId()); - } - }); - new ColumnViewerComparator(column); - - // State - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(18); - column.getColumn().setText("State"); - column.setLabelProvider(new StateLabelProvider()); - new ColumnViewerComparator(column); - - // Symbolic name - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(250); - column.getColumn().setText("Symbolic Name"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -4280840684440451080L; - - public String getText(Object element) { - return ((Bundle) element).getSymbolicName(); - } - }); - new ColumnViewerComparator(column); - - // Version - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(250); - column.getColumn().setText("Version"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = 6871926308708629989L; - - public String getText(Object element) { - Bundle bundle = (org.osgi.framework.Bundle) element; - return bundle.getVersion().toString(); - } - }); - new ColumnViewerComparator(column); - - viewer.setInput(bc); - - } - - @Focus - public void setFocus() { - if (viewer != null) - viewer.getControl().setFocus(); - } - - /** Content provider managing the array of bundles */ - private static class BundleContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -8533792785725875977L; - - public Object[] getElements(Object inputElement) { - if (inputElement instanceof BundleContext) { - BundleContext bc = (BundleContext) inputElement; - return bc.getBundles(); - } - return null; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java deleted file mode 100644 index 8a3605095..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java +++ /dev/null @@ -1,173 +0,0 @@ -//package org.argeo.eclipse.ui.workbench.osgi; -//public class BundlesView {} - -package org.argeo.cms.e4.monitoring; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.naming.ldap.LdapName; - -import org.argeo.api.cms.CmsSession; -import org.argeo.eclipse.ui.ColumnViewerComparator; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.argeo.util.LangUtils; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** - * Overview of the active CMS sessions. - */ -public class CmsSessionsView { - private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionsView.class).getBundleContext(); - - private TableViewer viewer; - - @PostConstruct - public void createPartControl(Composite parent) { - viewer = new TableViewer(parent); - viewer.setContentProvider(new CmsSessionContentProvider()); - viewer.getTable().setHeaderVisible(true); - - EclipseUiSpecificUtils.enableToolTipSupport(viewer); - - int longColWidth = 150; - int smallColWidth = 100; - - // Display name - TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(longColWidth); - column.getColumn().setText("User"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getDisplayName(); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getUserDn().toString(); - } - }); - new ColumnViewerComparator(column); - - // Creation time - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Since"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return LangUtils.since(((CmsSession) element).getCreationTime()); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getCreationTime().toString(); - } - }); - new ColumnViewerComparator(column); - - // Username - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Username"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - LdapName userDn = ((CmsSession) element).getUserDn(); - return userDn.getRdn(userDn.size() - 1).getValue().toString(); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getUserDn().toString(); - } - }); - new ColumnViewerComparator(column); - - // UUID - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("UUID"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getUuid().toString(); - } - - public String getToolTipText(Object element) { - return getText(element); - } - }); - new ColumnViewerComparator(column); - - // Local ID - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Local ID"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getLocalId(); - } - - public String getToolTipText(Object element) { - return getText(element); - } - }); - new ColumnViewerComparator(column); - - viewer.setInput(bc); - - } - - @Focus - public void setFocus() { - if (viewer != null) - viewer.getControl().setFocus(); - } - - /** Content provider managing the array of bundles */ - private static class CmsSessionContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -8533792785725875977L; - - public Object[] getElements(Object inputElement) { - if (inputElement instanceof BundleContext) { - BundleContext bc = (BundleContext) inputElement; - Collection> srs; - try { - srs = bc.getServiceReferences(CmsSession.class, null); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Cannot retrieve CMS sessions", e); - } - List res = new ArrayList<>(); - for (ServiceReference sr : srs) { - res.add(bc.getService(sr)); - } - return res.toArray(); - } - return null; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java deleted file mode 100644 index f0d8c2952..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java +++ /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 modules = new ArrayList(); - for (Bundle bundle : bundles) { - if (bundle.getState() == Bundle.ACTIVE) - modules.add(new BundleNode(bundle, true)); - } - return modules.toArray(); - } else if (parentElement instanceof TreeParent) { - return ((TreeParent) parentElement).getChildren(); - } else { - return null; - } - } - - public Object getParent(Object element) { - // TODO Auto-generated method stub - return null; - } - - public boolean hasChildren(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).hasChildren(); - } - return false; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } - - private class ModulesLabelProvider extends StateLabelProvider { - private static final long serialVersionUID = 5290046145534824722L; - - @Override - public String getText(Object element) { - if (element instanceof BundleNode) - return element.toString() + " [" + ((BundleNode) element).getBundle().getBundleId() + "]"; - return element.toString(); - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java deleted file mode 100644 index 759b3e955..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java +++ /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 res = new ArrayList<>(); - Configuration configuration = (Configuration) parentElement; - Dictionary 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 { - - @Override - public int compare(Configuration o1, Configuration o2) { - return o1.getPid().compareTo(o2.getPid()); - } - - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java deleted file mode 100644 index 7217fe612..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.swt.graphics.Image; - -/** Shared icons. */ -public class OsgiExplorerImages extends CmsImages { - public final static Image INSTALLED = createIcon("installed.gif"); - public final static Image RESOLVED = createIcon("resolved.gif"); - public final static Image STARTING = createIcon("starting.gif"); - public final static Image ACTIVE = createIcon("active.gif"); - public final static Image SERVICE_PUBLISHED = createIcon("service_published.gif"); - public final static Image SERVICE_REFERENCED = createIcon("service_referenced.gif"); - public final static Image CONFIGURATION = createIcon("node.gif"); -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java deleted file mode 100644 index d9c45fe11..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.argeo.eclipse.ui.TreeParent; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; - -/** A tree element representing a {@link ServiceReference} */ -@SuppressWarnings({ "rawtypes" }) -class ServiceReferenceNode extends TreeParent { - private final ServiceReference serviceReference; - private final boolean published; - - public ServiceReferenceNode(ServiceReference serviceReference, - boolean published) { - super(serviceReference.toString()); - this.serviceReference = serviceReference; - this.published = published; - - if (isPublished()) { - Bundle[] usedBundles = serviceReference.getUsingBundles(); - if (usedBundles != null) { - for (Bundle b : usedBundles) { - if (b != null) - addChild(new BundleNode(b)); - } - } - } else { - Bundle provider = serviceReference.getBundle(); - addChild(new BundleNode(provider)); - } - - for (String key : serviceReference.getPropertyKeys()) { - addChild(new TreeParent(key + "=" - + serviceReference.getProperty(key))); - } - - } - - public ServiceReference getServiceReference() { - return serviceReference; - } - - public boolean isPublished() { - return published; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java deleted file mode 100644 index 5cb5b6563..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java +++ /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 "<>"; - return "STARTING"; - case Bundle.STOPPING: - return "STOPPING"; - case Bundle.ACTIVE: - return "ACTIVE"; - default: - return null; - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java deleted file mode 100644 index 873bf3118..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Monitoring perspective. */ -package org.argeo.cms.e4.monitoring; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java deleted file mode 100644 index 233119c0d..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Eclipse 4 user interfaces. */ -package org.argeo.cms.e4; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java deleted file mode 100644 index 5a805d1e9..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.cms.e4.parts; - -import java.security.AccessController; -import java.time.ZonedDateTime; - -import javax.annotation.PostConstruct; -import javax.security.auth.Subject; - -import org.argeo.api.cms.CmsSession; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** A canonical view of the logged in user. */ -public class EgoDashboard { - private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext(); - - @PostConstruct - public void createPartControl(Composite p) { - p.setLayout(new GridLayout()); - String username = CurrentUser.getUsername(); - - CmsSwtUtils.lbl(p, "" + CurrentUser.getDisplayName() + ""); - CmsSwtUtils.txt(p, username); - CmsSwtUtils.lbl(p, "Roles:"); - roles: for (String role : CurrentUser.roles()) { - if (username.equals(role)) - continue roles; - CmsSwtUtils.txt(p, role); - } - - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject != null) { - CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bc, subject); - ZonedDateTime loggedIndSince = cmsSession.getCreationTime(); - CmsSwtUtils.lbl(p, "Session:"); - CmsSwtUtils.txt(p, cmsSession.getUuid().toString()); - CmsSwtUtils.lbl(p, "Logged in since:"); - CmsSwtUtils.txt(p, loggedIndSince.toString()); - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java deleted file mode 100644 index 137f76242..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.cms.ui.eclipse.forms.ManagedForm; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.e4.ui.di.Persist; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Editor for a user, might be a user or a group. */ -public abstract class AbstractRoleEditor { - - // public final static String USER_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".userEditor"; - // public final static String GROUP_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".groupEditor"; - - /* DEPENDENCY INJECTION */ - @Inject - protected UserAdminWrapper userAdminWrapper; - - @Inject - private MPart mPart; - - // @Inject - // Composite parent; - - private UserAdmin userAdmin; - - // Context - private User user; - private String username; - - private NameChangeListener listener; - - private ManagedForm managedForm; - - // public void init(IEditorSite site, IEditorInput input) throws - // PartInitException { - @PostConstruct - public void init(Composite parent) { - this.userAdmin = userAdminWrapper.getUserAdmin(); - username = mPart.getPersistedState().get(LdapAttrs.uid.name()); - user = (User) userAdmin.getRole(username); - - listener = new NameChangeListener(Display.getCurrent()); - userAdminWrapper.addListener(listener); - updateEditorTitle(null); - - managedForm = new ManagedForm(parent) { - - @Override - public void staleStateChanged() { - refresh(); - } - }; - ScrolledComposite scrolled = managedForm.getForm(); - Composite body = new Composite(scrolled, SWT.NONE); - scrolled.setContent(body); - createUi(body); - managedForm.refresh(); - } - - abstract void createUi(Composite parent); - - /** - * returns the list of all authorizations for the given user or of the current - * displayed user if parameter is null - */ - protected List getFlatGroups(User aUser) { - Authorization currAuth; - if (aUser == null) - currAuth = userAdmin.getAuthorization(this.user); - else - currAuth = userAdmin.getAuthorization(aUser); - - String[] roles = currAuth.getRoles(); - - List groups = new ArrayList(); - for (String roleStr : roles) { - User currRole = (User) userAdmin.getRole(roleStr); - if (currRole != null && !groups.contains(currRole)) - groups.add(currRole); - } - return groups; - } - - protected IManagedForm getManagedForm() { - return managedForm; - } - - /** Exposes the user (or group) that is displayed by the current editor */ - protected User getDisplayedUser() { - return user; - } - - private void setDisplayedUser(User user) { - this.user = user; - } - - void updateEditorTitle(String title) { - if (title == null) { - String commonName = UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); - title = "".equals(commonName) ? user.getName() : commonName; - } - setPartName(title); - } - - protected void setPartName(String name) { - mPart.setLabel(name); - } - - // protected void addPages() { - // try { - // if (user.getType() == Role.GROUP) - // addPage(new GroupMainPage(this, userAdminWrapper, repository, nodeInstance)); - // else - // addPage(new UserMainPage(this, userAdminWrapper)); - // } catch (Exception e) { - // throw new CmsException("Cannot add pages", e); - // } - // } - - @Persist - public void doSave(IProgressMonitor monitor) { - userAdminWrapper.beginTransactionIfNeeded(); - commitPages(true); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - // firePropertyChange(PROP_DIRTY); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); - } - - protected void commitPages(boolean b) { - managedForm.commit(b); - } - - @PreDestroy - public void dispose() { - userAdminWrapper.removeListener(listener); - managedForm.dispose(); - } - - // CONTROLERS FOR THIS EDITOR AND ITS PAGES - - class NameChangeListener extends UiUserAdminListener { - public NameChangeListener(Display display) { - super(display); - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - Role changedRole = event.getRole(); - if (changedRole == null || changedRole.equals(user)) { - updateEditorTitle(null); - User reloadedUser = (User) userAdminWrapper.getUserAdmin().getRole(user.getName()); - setDisplayedUser(reloadedUser); - } - } - } - - class MainInfoListener extends UiUserAdminListener { - private final AbstractFormPart part; - - public MainInfoListener(Display display, AbstractFormPart part) { - super(display); - this.part = part; - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - // Rollback - if (event.getRole() == null) - part.markStale(); - } - } - - class GroupChangeListener extends UiUserAdminListener { - private final AbstractFormPart part; - - public GroupChangeListener(Display display, AbstractFormPart part) { - super(display); - this.part = part; - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - // always mark as stale - part.markStale(); - } - } - - /** Registers a listener that will notify this part */ - class FormPartML implements ModifyListener { - private static final long serialVersionUID = 6299808129505381333L; - private AbstractFormPart formPart; - - public FormPartML(AbstractFormPart generalPart) { - this.formPart = generalPart; - } - - public void modifyText(ModifyEvent e) { - // Discard event when the control does not have the focus, typically - // to avoid all editors being marked as dirty during a Rollback - if (((Control) e.widget).isFocusControl()) - formPart.markDirty(); - } - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } - - /** Creates label and multiline text. */ - Text createLMT(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - Text text = new Text(parent, SWT.NONE); - text.setText(value); - text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, true)); - return text; - } - - /** Creates label and password. */ - Text createLP(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - Text text = new Text(parent, SWT.PASSWORD | SWT.BORDER); - text.setText(value); - text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); - return text; - } - - /** Creates label and text. */ - Text createLT(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - Text text = new Text(parent, SWT.BORDER); - text.setText(value); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - return text; - } - - Text createReadOnlyLT(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - Text text = new Text(parent, SWT.NONE); - text.setText(value); - text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); - text.setEditable(false); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - return text; - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java deleted file mode 100644 index 07df312e1..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.e4.users; - -/** Centralize the declaration of Workbench specific CSS Styles */ -interface CmsWorkbenchStyles { - - // Specific People layouting - String WORKBENCH_FORM_TEXT = "workbench_form_text"; -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java deleted file mode 100644 index a011c5f62..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java +++ /dev/null @@ -1,566 +0,0 @@ -package org.argeo.cms.e4.users; - -import static org.argeo.api.cms.CmsContext.WORKGROUP; -import static org.argeo.cms.auth.UserAdminUtils.setProperty; -import static org.argeo.util.naming.LdapAttrs.businessCategory; -import static org.argeo.util.naming.LdapAttrs.description; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsContext; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.MailLP; -import org.argeo.cms.e4.users.providers.RoleIconLP; -import org.argeo.cms.e4.users.providers.UserFilter; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ToolBarManager; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.ViewerDropAdapter; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.dnd.TransferData; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Link; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.ToolBar; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -//import org.eclipse.ui.forms.AbstractFormPart; -//import org.eclipse.ui.forms.IManagedForm; -//import org.eclipse.ui.forms.SectionPart; -//import org.eclipse.ui.forms.editor.FormEditor; -//import org.eclipse.ui.forms.editor.FormPage; -//import org.eclipse.ui.forms.widgets.FormToolkit; -//import org.eclipse.ui.forms.widgets.ScrolledForm; -//import org.eclipse.ui.forms.widgets.Section; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Display/edit main properties of a given group */ -public class GroupEditor extends AbstractRoleEditor { - // final static String ID = "GroupEditor.mainPage"; - - @Inject - private EPartService partService; - - // private final UserEditor editor; - @Inject - private Repository repository; - @Inject - private CmsContext nodeInstance; - // private final UserAdminWrapper userAdminWrapper; - private Session groupsSession; - - // public GroupMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper, - // Repository repository, - // NodeInstance nodeInstance) { - // super(editor, ID, "Main"); - // try { - // session = repository.login(); - // } catch (RepositoryException e) { - // throw new CmsException("Cannot retrieve session of in MainGroupPage - // constructor", e); - // } - // this.editor = (UserEditor) editor; - // this.userAdminWrapper = userAdminWrapper; - // this.nodeInstance = nodeInstance; - // } - - // protected void createFormContent(final IManagedForm mf) { - // ScrolledForm form = mf.getForm(); - // Composite body = form.getBody(); - // GridLayout mainLayout = new GridLayout(); - // body.setLayout(mainLayout); - // Group group = (Group) editor.getDisplayedUser(); - // appendOverviewPart(body, group); - // appendMembersPart(body, group); - // } - - @Override - protected void createUi(Composite parent) { - try { - groupsSession = repository.login(CmsConstants.SRV_WORKSPACE); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve session", e); - } - // ScrolledForm form = mf.getForm(); - // Composite body = form.getBody(); - // Composite body = new Composite(parent, SWT.NONE); - Composite body = parent; - GridLayout mainLayout = new GridLayout(); - body.setLayout(mainLayout); - Group group = (Group) getDisplayedUser(); - appendOverviewPart(body, group); - appendMembersPart(body, group); - } - - @PreDestroy - public void dispose() { - JcrUtils.logoutQuietly(groupsSession); - super.dispose(); - } - - /** Creates the general section */ - protected void appendOverviewPart(final Composite parent, final Group group) { - Composite body = new Composite(parent, SWT.NONE); - // GridLayout layout = new GridLayout(5, false); - GridLayout layout = new GridLayout(2, false); - body.setLayout(layout); - body.setLayoutData(CmsSwtUtils.fillWidth()); - - String cn = UserAdminUtils.getProperty(group, LdapAttrs.cn.name()); - createReadOnlyLT(body, "Name", cn); - createReadOnlyLT(body, "DN", group.getName()); - createReadOnlyLT(body, "Domain", UserAdminUtils.getDomainName(group)); - - // Description - Label descLbl = new Label(body, SWT.LEAD); - descLbl.setFont(EclipseUiUtils.getBoldFont(body)); - descLbl.setText("Description"); - descLbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false, 2, 1)); - final Text descTxt = new Text(body, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); - GridData gd = EclipseUiUtils.fillWidth(); - gd.heightHint = 50; - gd.horizontalSpan = 2; - descTxt.setLayoutData(gd); - - // Mark as workgroup - Link markAsWorkgroupLk = new Link(body, SWT.NONE); - markAsWorkgroupLk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); - - // create form part (controller) - final AbstractFormPart part = new AbstractFormPart() { - - private MainInfoListener listener; - - @Override - public void initialize(IManagedForm form) { - super.initialize(form); - listener = new MainInfoListener(parent.getDisplay(), this); - userAdminWrapper.addListener(listener); - } - - @Override - public void dispose() { - userAdminWrapper.removeListener(listener); - super.dispose(); - } - - public void commit(boolean onSave) { - // group.getProperties().put(LdapAttrs.description.name(), descTxt.getText()); - setProperty(group, description, descTxt.getText()); - super.commit(onSave); - } - - @Override - public void refresh() { - // dnTxt.setText(group.getName()); - // cnTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.cn.name())); - descTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.description.name())); - Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); - if (workgroupHome == null) - markAsWorkgroupLk.setText("Mark as workgroup"); - 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 columnDefs = new ArrayList(); - 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 columnDefs = new ArrayList(); - // 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 listFilteredElements(String filter) { - // reload user and set it in the editor - Group group = (Group) getDisplayedUser(); - Role[] roles = group.getMembers(); - List users = new ArrayList(); - 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 it = ((IStructuredSelection) selection).iterator(); - List users = new ArrayList(); - 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 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 newGroupMemberships = getFlatGroups(newGroup); - if (newGroupMemberships.contains(myGroup)) { - MessageDialog.openError(shell, "Forbidden addition", - "Cannot add " + newUserName + " to group " + myName + ", this membership already exists"); - return; - } - userAdminWrapper.beginTransactionIfNeeded(); - myGroup.addMember(newGroup); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); - } else if (role.getType() == Role.USER) { - // TODO check if the group is already member of this group - WorkTransaction transaction = userAdminWrapper.beginTransactionIfNeeded(); - User user = (User) role; - myGroup.addMember(user); - if (UserAdminWrapper.COMMIT_ON_SAVE) - try { - transaction.commit(); - } catch (Exception e) { - throw new IllegalStateException( - "Cannot commit transaction " + "after user group membership update", e); - } - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); - } - super.drop(event); - } - - @Override - public boolean performDrop(Object data) { - // myUserViewerCmp.refresh(); - return true; - } - } - - // LOCAL HELPERS - // private Composite addSection(FormToolkit tk, Composite parent) { - // Section section = tk.createSection(parent, SWT.NO_FOCUS); - // section.setLayoutData(EclipseUiUtils.fillWidth()); - // Composite body = tk.createComposite(section, SWT.WRAP); - // body.setLayoutData(EclipseUiUtils.fillAll()); - // section.setClient(body); - // return body; - // } - - /** Creates label and text. */ - // private Text createLT(Composite parent, String label, String value) { - // FormToolkit toolkit = getManagedForm().getToolkit(); - // Label lbl = toolkit.createLabel(parent, label); - // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - // Text text = toolkit.createText(parent, value, SWT.BORDER); - // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - // return text; - // } - // - // Text createReadOnlyLT(Composite parent, String label, String value) { - // FormToolkit toolkit = getManagedForm().getToolkit(); - // Label lbl = toolkit.createLabel(parent, label); - // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - // Text text = toolkit.createText(parent, value, SWT.NONE); - // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - // text.setEditable(false); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - // return text; - // } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java deleted file mode 100644 index ddad34fd1..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java +++ /dev/null @@ -1,251 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.RoleIconLP; -import org.argeo.cms.e4.users.providers.UserDragListener; -//import org.argeo.cms.ui.workbench.WorkbenchUiPlugin; -//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener; -//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -//import org.eclipse.ui.part.ViewPart; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** List all groups with filter */ -public class GroupsView { - private final static CmsLog log = CmsLog.getLog(GroupsView.class); - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView"; - - @Inject - private EPartService partService; - @Inject - private UserAdminWrapper userAdminWrapper; - - // UI Objects - private LdifUsersTable groupTableViewerCmp; - private TableViewer userViewer; - private List columnDefs = new ArrayList(); - - 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 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 users = new ArrayList(); - for (Role role : roles) - if (!users.contains(role)) - users.add((User) role); - else - log.warn("Duplicated role: " + role); - - return users; - } - } - - public void refresh() { - groupTableViewerCmp.refresh(); - } - - @PreDestroy - public void dispose() { - userAdminWrapper.removeListener(listener); - } - - @Focus - public void setFocus() { - groupTableViewerCmp.setFocus(); - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java deleted file mode 100644 index 7bbe3c727..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; - -/** Shared icons that must be declared programmatically . */ -public class SecurityAdminImages extends CmsImages { - private final static String PREFIX = "icons/"; - - public final static ImageDescriptor ICON_REMOVE_DESC = createDesc(PREFIX + "delete.png"); - public final static ImageDescriptor ICON_USER_DESC = createDesc(PREFIX + "person.png"); - - public final static Image ICON_USER = ICON_USER_DESC.createImage(); - public final static Image ICON_GROUP = createImg(PREFIX + "group.png"); - public final static Image ICON_WORKGROUP = createImg(PREFIX + "workgroup.png"); - public final static Image ICON_ROLE = createImg(PREFIX + "role.gif"); - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java deleted file mode 100644 index f85649260..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.argeo.osgi.transaction.WorkTransaction; - -/** First effort to centralize back end methods used by the user admin UI */ -public class UiAdminUtils { - /* - * INTERNAL METHODS: Below methods are meant to stay here and are not part - * of a potential generic backend to manage the useradmin - */ - /** Easily notify the ActiveWindow that the transaction had a state change */ - public final static void notifyTransactionStateChange( - WorkTransaction userTransaction) { -// try { -// IWorkbenchWindow aww = PlatformUI.getWorkbench() -// .getActiveWorkbenchWindow(); -// ISourceProviderService sourceProviderService = (ISourceProviderService) aww -// .getService(ISourceProviderService.class); -// UserTransactionProvider esp = (UserTransactionProvider) sourceProviderService -// .getSourceProvider(UserTransactionProvider.TRANSACTION_STATE); -// esp.fireTransactionStateChange(); -// } catch (Exception e) { -// throw new CmsException("Unable to begin transaction", e); -// } - } - - /** - * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}. - * Thanks to this tip. - */ - public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java deleted file mode 100644 index eb64aba0e..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** Convenience class to insure the call to refresh is done in the UI thread */ -public abstract class UiUserAdminListener implements UserAdminListener { - - private final Display display; - - public UiUserAdminListener(Display display) { - this.display = display; - } - - @Override - public void roleChanged(final UserAdminEvent event) { - display.asyncExec(new Runnable() { - @Override - public void run() { - roleChangedToUiThread(event); - } - }); - } - - public abstract void roleChangedToUiThread(UserAdminEvent event); -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java deleted file mode 100644 index 16aa78316..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.osgi.useradmin.UserDirectory; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** Centralise interaction with the UserAdmin in this bundle */ -public class UserAdminWrapper { - - private UserAdmin userAdmin; - // private ServiceReference userAdminServiceReference; -// private Set uris; - private Map> 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 listeners = new ArrayList(); - - /** - * 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 getKnownBaseDns(boolean onlyWritable) { - Map dns = new HashMap(); - 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 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 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 properties) { - userDirectories.put(userDirectory, new Hashtable<>(properties)); - } - - public void removeUserDirectory(UserDirectory userDirectory, Map properties) { - userDirectories.remove(userDirectory); - } - - // public void setUserAdminServiceReference( - // ServiceReference userAdminServiceReference) { - // this.userAdminServiceReference = userAdminServiceReference; - // } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java deleted file mode 100644 index a38d171ef..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java +++ /dev/null @@ -1,606 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.MailLP; -import org.argeo.cms.e4.users.providers.UserNameLP; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.jface.dialogs.IPageChangeProvider; -import org.eclipse.jface.dialogs.IPageChangedListener; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.PageChangedEvent; -import org.eclipse.jface.wizard.IWizardContainer; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Text; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Wizard to update users */ -public class UserBatchUpdateWizard extends Wizard { - - private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class); - private UserAdminWrapper userAdminWrapper; - - // pages - private ChooseCommandWizardPage chooseCommandPage; - private ChooseUsersWizardPage userListPage; - private ValidateAndLaunchWizardPage validatePage; - - // Various implemented commands keys - private final static String CMD_UPDATE_PASSWORD = "resetPassword"; - private final static String CMD_UPDATE_EMAIL = "resetEmail"; - private final static String CMD_GROUP_MEMBERSHIP = "groupMembership"; - - private final Map commands = new HashMap() { - 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 usersToUpdate; - - public ResetPassword(UserAdminWrapper userAdminWrapper, List 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 usersToUpdate; - - public ResetEmail(UserAdminWrapper userAdminWrapper, List 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 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 usersToUpdate; - // - // protected abstract void doUpdate(User user); - // - // public UpdateJob(UserAdminWrapper userAdminWrapper, - // List 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 columnDefs = new ArrayList(); - 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 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 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 users = new ArrayList(); - 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 columnDefs = new ArrayList(); - 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 listFilteredElements(String filter) { - return userListPage.getSelectedUsers(); - } - } - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java deleted file mode 100644 index 66f442082..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java +++ /dev/null @@ -1,535 +0,0 @@ -package org.argeo.cms.e4.users; - -import static org.argeo.cms.auth.UserAdminUtils.getProperty; -import static org.argeo.util.naming.LdapAttrs.cn; -import static org.argeo.util.naming.LdapAttrs.givenName; -import static org.argeo.util.naming.LdapAttrs.mail; -import static org.argeo.util.naming.LdapAttrs.sn; -import static org.argeo.util.naming.LdapAttrs.uid; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.inject.Inject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.RoleIconLP; -import org.argeo.cms.e4.users.providers.UserFilter; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; -//import org.argeo.cms.ui.eclipse.forms.FormToolkit; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ToolBarManager; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerDropAdapter; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.dnd.TransferData; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Link; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.ToolBar; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Display/edit the properties of a given user */ -public class UserEditor extends AbstractRoleEditor { - // final static String ID = "UserEditor.mainPage"; - - @Inject - private EPartService partService; - - // private final UserEditor editor; - // private UserAdminWrapper userAdminWrapper; - - // Local configuration - // private final int PRE_TITLE_INDENT = 10; - - // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) { - // super(editor, ID, "Main"); - // this.editor = (UserEditor) editor; - // this.userAdminWrapper = userAdminWrapper; - // } - - // protected void createFormContent(final IManagedForm mf) { - // ScrolledForm form = mf.getForm(); - // Composite body = form.getBody(); - // GridLayout mainLayout = new GridLayout(); - // // mainLayout.marginRight = 10; - // body.setLayout(mainLayout); - // User user = editor.getDisplayedUser(); - // appendOverviewPart(body, user); - // // Remove to ability to force the password for his own user. The user - // // must then use the change pwd feature - // appendMemberOfPart(body, user); - // } - - @Override - protected void createUi(Composite body) { - // Composite body = new Composite(parent, SWT.BORDER); - GridLayout mainLayout = new GridLayout(); - // mainLayout.marginRight = 10; - body.setLayout(mainLayout); - // body.getParent().setLayout(new GridLayout()); - // body.setLayoutData(CmsUiUtils.fillAll()); - User user = getDisplayedUser(); - appendOverviewPart(body, user); - // Remove to ability to force the password for his own user. The user - // must then use the change pwd feature - appendMemberOfPart(body, user); - } - - /** Creates the general section */ - private void appendOverviewPart(final Composite parent, final User user) { - // FormToolkit tk = getManagedForm().getToolkit(); - - // Section section = tk.createSection(parent, SWT.NO_FOCUS); - // GridData gd = EclipseUiUtils.fillWidth(); - // // gd.verticalAlignment = PRE_TITLE_INDENT; - // section.setLayoutData(gd); - Composite body = new Composite(parent, SWT.NONE); - body.setLayoutData(EclipseUiUtils.fillWidth()); - // section.setClient(body); - // body.setLayout(new GridLayout(6, false)); - body.setLayout(new GridLayout(2, false)); - - Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn)); - Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid)); - Text firstName = createLT(body, "First name", getProperty(user, givenName)); - Text lastName = createLT(body, "Last name", getProperty(user, sn)); - Text email = createLT(body, "Email", getProperty(user, mail)); - - Link resetPwdLk = new Link(body, SWT.NONE); - if (!UserAdminUtils.isCurrentUser(user)) { - resetPwdLk.setText("Reset password"); - } - 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 columnDefs = new ArrayList(); - 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 listFilteredElements(String filter) { - List users = (List) getFlatGroups(null); - List filteredUsers = new ArrayList(); - 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 it = ((IStructuredSelection) selection).iterator(); - List groups = new ArrayList(); - while (it.hasNext()) { - Group currGroup = it.next(); - groups.add(currGroup); - } - - userAdminWrapper.beginTransactionIfNeeded(); - for (Group group : groups) { - group.removeMember(user); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - for (Group group : groups) { - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); - } - } - } - - /** - * Defines the table as being a potential target to add group memberships - * (roles) to this user - */ - private class GroupDropListener extends ViewerDropAdapter { - private static final long serialVersionUID = 2893468717831451621L; - - private final UserAdminWrapper myUserAdminWrapper; - private final User myUser; - - public GroupDropListener(UserAdminWrapper userAdminWrapper, Viewer userViewer, User user) { - super(userViewer); - this.myUserAdminWrapper = userAdminWrapper; - this.myUser = user; - } - - @Override - public boolean validateDrop(Object target, int operation, TransferData transferType) { - // Target is always OK in a list only view - // TODO check if not a string - boolean validDrop = true; - return validDrop; - } - - @Override - public void drop(DropTargetEvent event) { - String name = (String) event.data; - UserAdmin myUserAdmin = myUserAdminWrapper.getUserAdmin(); - Role role = myUserAdmin.getRole(name); - // TODO this check should be done before. - if (role.getType() == Role.GROUP) { - // TODO check if the user is already member of this group - - myUserAdminWrapper.beginTransactionIfNeeded(); - Group group = (Group) role; - group.addMember(myUser); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - myUserAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); - } - super.drop(event); - } - - @Override - public boolean performDrop(Object data) { - // userTableViewerCmp.refresh(); - return true; - } - } - - // LOCAL HELPERS - private void refreshFormTitle(User group) { - // getManagedForm().getForm().setText(UserAdminUtils.getProperty(group, - // LdapAttrs.cn.name())); - } - - /** Appends a section with a title */ - // private Section addSection(FormToolkit tk, Composite parent, String title) { - // Section section = tk.createSection(parent, Section.TITLE_BAR); - // GridData gd = EclipseUiUtils.fillWidth(); - // gd.verticalAlignment = PRE_TITLE_INDENT; - // section.setLayoutData(gd); - // section.setText(title); - // // section.getMenu().setVisible(true); - // - // Composite body = tk.createComposite(section, SWT.WRAP); - // body.setLayoutData(EclipseUiUtils.fillAll()); - // section.setClient(body); - // - // return section; - // } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java deleted file mode 100644 index c6d024ebc..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.argeo.cms.e4.CmsE4Utils; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.User; - -/** - * Default double click listener for the various user tables, will open the - * clicked item in the editor - */ -public class UserTableDefaultDClickListener implements IDoubleClickListener { - private final EPartService partService; - - public UserTableDefaultDClickListener(EPartService partService) { - this.partService = partService; - } - - public void doubleClick(DoubleClickEvent evt) { - if (evt.getSelection().isEmpty()) - return; - Object obj = ((IStructuredSelection) evt.getSelection()).getFirstElement(); - User user = (User) obj; - - String editorId = getEditorId(user); - CmsE4Utils.openEditor(partService, editorId, LdapAttrs.uid.name(), user.getName()); - } - - protected String getEditorId(User user) { - if (user instanceof Group) - return "org.argeo.cms.e4.partdescriptor.groupEditor"; - else - return "org.argeo.cms.e4.partdescriptor.userEditor"; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java deleted file mode 100644 index 877a925c6..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.MailLP; -import org.argeo.cms.e4.users.providers.UserDragListener; -import org.argeo.cms.e4.users.providers.UserNameLP; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** List all users with filter - based on Ldif userAdmin */ -public class UsersView { - // private final static Log log = LogFactory.getLog(UsersView.class); - - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView"; - - @Inject - private UserAdminWrapper userAdminWrapper; - @Inject - private EPartService partService; - - // UI Objects - private LdifUsersTable userTableViewerCmp; - private TableViewer userViewer; - private List columnDefs = new ArrayList(); - - 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 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 users = new ArrayList(); - for (Role role : roles) - // if (role.getType() == Role.USER && role.getType() != - // Role.GROUP) - users.add((User) role); - return users; - } - } - - public void refresh() { - userTableViewerCmp.refresh(); - } - - // Override generic view methods - @PreDestroy - public void dispose() { - userAdminWrapper.removeListener(listener); - } - - @Focus - public void setFocus() { - userTableViewerCmp.setFocus(); - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java deleted file mode 100644 index 742bc3f5f..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java +++ /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 groups = new ArrayList(); - // Iterator it = ((IStructuredSelection) selection).iterator(); - - List selection = (List) selectionService.getSelection(); - if (selection == null) - return; - - StringBuilder builder = new StringBuilder(); - for (Group group : selection) { - Group currGroup = group; - String groupName = UserAdminUtils.getUserLocalId(currGroup.getName()); - // TODO add checks - builder.append(groupName).append("; "); - // groups.add(currGroup); - } - - if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Groups", "Are you sure that you " - + "want to delete these groups?\n" + builder.substring(0, builder.length() - 2))) - return; - - userAdminWrapper.beginTransactionIfNeeded(); - UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); - // IWorkbenchPage iwp = - // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); - for (Group group : selection) { - String groupName = group.getName(); - // TODO find a way to close the editor cleanly if opened. Cannot be - // done through the UserAdminListeners, it causes a - // java.util.ConcurrentModificationException because disposing the - // editor unregisters and disposes the listener - // IEditorPart part = iwp.findEditor(new UserEditorInput(groupName)); - // if (part != null) - // iwp.closeEditor(part, false); - userAdmin.removeRole(groupName); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - - // Update the view - for (Group group : selection) { - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, group)); - } - - // return null; - } - - @CanExecute - public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - return part.getObject() instanceof GroupsView && selectionService.getSelection() != null; - } - - /* DEPENDENCY INJECTION */ - // public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - // this.userAdminWrapper = userAdminWrapper; - // } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java deleted file mode 100644 index d1afd2210..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java +++ /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 selection = (List) selectionService.getSelection(); - if (selection == null) - return; - -// Iterator it = ((IStructuredSelection) selection).iterator(); -// List users = new ArrayList(); - StringBuilder builder = new StringBuilder(); - - for(User user:selection) { - User currUser = user; -// User currUser = it.next(); - String userName = UserAdminUtils.getUserLocalId(currUser.getName()); - if (UserAdminUtils.isCurrentUser(currUser)) { - MessageDialog.openError(Display.getCurrent().getActiveShell(), "Deletion forbidden", - "You cannot delete your own user this way."); - return; - } - builder.append(userName).append("; "); -// users.add(currUser); - } - - if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Users", - "Are you sure that you want to delete these users?\n" + builder.substring(0, builder.length() - 2))) - return; - - userAdminWrapper.beginTransactionIfNeeded(); - UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); - // IWorkbenchPage iwp = - // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); - - for (User user : selection) { - String userName = user.getName(); - // TODO find a way to close the editor cleanly if opened. Cannot be - // done through the UserAdminListeners, it causes a - // java.util.ConcurrentModificationException because disposing the - // editor unregisters and disposes the listener - // IEditorPart part = iwp.findEditor(new UserEditorInput(userName)); - // if (part != null) - // iwp.closeEditor(part, false); - userAdmin.removeRole(userName); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - - for (User user : selection) { - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); - } - } - - @CanExecute - public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - return part.getObject() instanceof UsersView && selectionService.getSelection() != null; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java deleted file mode 100644 index d2ffa791b..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.argeo.cms.e4.users.handlers; - -import java.util.Dictionary; -import java.util.Map; - -import javax.inject.Inject; - -import org.argeo.cms.CmsException; -import org.argeo.cms.e4.users.UserAdminWrapper; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.jface.wizard.WizardDialog; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Create a new group */ -public class NewGroup { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newGroup"; - - /* DEPENDENCY INJECTION */ - @Inject - private UserAdminWrapper userAdminWrapper; - - @Execute - public Object execute() { - NewGroupWizard newGroupWizard = new NewGroupWizard(); - newGroupWizard.setWindowTitle("Group creation"); - WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newGroupWizard); - dialog.open(); - return null; - } - - private class NewGroupWizard extends Wizard { - - // Pages - private MainGroupInfoWizardPage mainGroupInfo; - - // UI fields - private Text dNameTxt, commonNameTxt, descriptionTxt; - private Combo baseDnCmb; - - public NewGroupWizard() { - } - - @Override - public void addPages() { - mainGroupInfo = new MainGroupInfoWizardPage(); - addPage(mainGroupInfo); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public boolean performFinish() { - if (!canFinish()) - return false; - String commonName = commonNameTxt.getText(); - try { - userAdminWrapper.beginTransactionIfNeeded(); - String dn = getDn(commonName); - Group group = (Group) userAdminWrapper.getUserAdmin().createRole(dn, Role.GROUP); - Dictionary props = group.getProperties(); - String descStr = descriptionTxt.getText(); - if (EclipseUiUtils.notEmpty(descStr)) - props.put(LdapAttrs.description.name(), descStr); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, group)); - return true; - } catch (Exception e) { - ErrorFeedback.show("Cannot create new group " + commonName, e); - return false; - } - } - - private class MainGroupInfoWizardPage extends WizardPage implements FocusListener { - private static final long serialVersionUID = -3150193365151601807L; - - public MainGroupInfoWizardPage() { - super("Main"); - setTitle("General information"); - setMessage("Please choose a domain, provide a common name " + "and a free description"); - } - - @Override - public void createControl(Composite parent) { - Composite bodyCmp = new Composite(parent, SWT.NONE); - setControl(bodyCmp); - bodyCmp.setLayout(new GridLayout(2, false)); - - dNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Distinguished name"); - dNameTxt.setEnabled(false); - - baseDnCmb = createGridLC(bodyCmp, "Base DN"); - // Initialise before adding the listener to avoid NPE - initialiseDnCmb(baseDnCmb); - baseDnCmb.addFocusListener(this); - - commonNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Common name"); - commonNameTxt.addFocusListener(this); - - Label descLbl = new Label(bodyCmp, SWT.LEAD); - descLbl.setText("Description"); - descLbl.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false)); - descriptionTxt = new Text(bodyCmp, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); - descriptionTxt.setLayoutData(EclipseUiUtils.fillAll()); - descriptionTxt.addFocusListener(this); - - // Initialize buttons - setPageComplete(false); - getContainer().updateButtons(); - } - - @Override - public void focusLost(FocusEvent event) { - String name = commonNameTxt.getText(); - if (EclipseUiUtils.isEmpty(name)) - dNameTxt.setText(""); - else - dNameTxt.setText(getDn(name)); - - String message = checkComplete(); - if (message != null) { - setMessage(message, WizardPage.ERROR); - setPageComplete(false); - } else { - setMessage("Complete", WizardPage.INFORMATION); - setPageComplete(true); - } - getContainer().updateButtons(); - } - - @Override - public void focusGained(FocusEvent event) { - } - - /** @return the error message or null if complete */ - protected String checkComplete() { - String name = commonNameTxt.getText(); - - if (name.trim().equals("")) - return "Common name must not be empty"; - Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); - if (role != null) - return "Group " + name + " already exists"; - return null; - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (visible) - if (baseDnCmb.getSelectionIndex() == -1) - baseDnCmb.setFocus(); - else - commonNameTxt.setFocus(); - } - } - - private Map getDns() { - return userAdminWrapper.getKnownBaseDns(true); - } - - private String getDn(String cn) { - Map dns = getDns(); - String bdn = baseDnCmb.getText(); - if (EclipseUiUtils.notEmpty(bdn)) { - Dictionary 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 dns = userAdminWrapper.getKnownBaseDns(true); - if (dns.isEmpty()) - throw new CmsException("No writable base dn found. Cannot create group"); - combo.setItems(dns.keySet().toArray(new String[0])); - if (dns.size() == 1) - combo.select(0); - } - } - - private Combo createGridLC(Composite parent, String label) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); - combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - return combo; - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java deleted file mode 100644 index 07d82c749..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.argeo.cms.e4.users.handlers; - -import java.util.Dictionary; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.UiAdminUtils; -import org.argeo.cms.e4.users.UserAdminWrapper; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.jface.wizard.WizardDialog; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Open a wizard that enables creation of a new user. */ -public class NewUser { - // private final static Log log = LogFactory.getLog(NewUser.class); - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newUser"; - - /* DEPENDENCY INJECTION */ - @Inject - private UserAdminWrapper userAdminWrapper; - - @Execute - public Object execute() { - NewUserWizard newUserWizard = new NewUserWizard(); - newUserWizard.setWindowTitle("User creation"); - WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newUserWizard); - dialog.open(); - return null; - } - - private class NewUserWizard extends Wizard { - - // pages - private MainUserInfoWizardPage mainUserInfo; - - // End user fields - private Text dNameTxt, usernameTxt, firstNameTxt, lastNameTxt, primaryMailTxt, pwd1Txt, pwd2Txt; - private Combo baseDnCmb; - - public NewUserWizard() { - - } - - @Override - public void addPages() { - mainUserInfo = new MainUserInfoWizardPage(); - addPage(mainUserInfo); - String message = "Default wizard that also eases user creation tests:\n " - + "Mail and last name are automatically " - + "generated form the uid. Password are defauted to 'demo'."; - mainUserInfo.setMessage(message, WizardPage.WARNING); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public boolean performFinish() { - if (!canFinish()) - return false; - String username = mainUserInfo.getUsername(); - userAdminWrapper.beginTransactionIfNeeded(); - try { - User user = (User) userAdminWrapper.getUserAdmin().createRole(getDn(username), Role.USER); - - Dictionary props = user.getProperties(); - - String lastNameStr = lastNameTxt.getText(); - if (EclipseUiUtils.notEmpty(lastNameStr)) - props.put(LdapAttrs.sn.name(), lastNameStr); - - String firstNameStr = firstNameTxt.getText(); - if (EclipseUiUtils.notEmpty(firstNameStr)) - props.put(LdapAttrs.givenName.name(), firstNameStr); - - String cn = UserAdminUtils.buildDefaultCn(firstNameStr, lastNameStr); - if (EclipseUiUtils.notEmpty(cn)) - props.put(LdapAttrs.cn.name(), cn); - - String mailStr = primaryMailTxt.getText(); - if (EclipseUiUtils.notEmpty(mailStr)) - props.put(LdapAttrs.mail.name(), mailStr); - - char[] password = mainUserInfo.getPassword(); - user.getCredentials().put(null, password); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, user)); - return true; - } catch (Exception e) { - ErrorFeedback.show("Cannot create new user " + username, e); - return false; - } - } - - private class MainUserInfoWizardPage extends WizardPage implements ModifyListener { - private static final long serialVersionUID = -3150193365151601807L; - - public MainUserInfoWizardPage() { - super("Main"); - setTitle("Required Information"); - } - - @Override - public void createControl(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - dNameTxt = EclipseUiUtils.createGridLT(composite, "Distinguished name", this); - dNameTxt.setEnabled(false); - - baseDnCmb = createGridLC(composite, "Base DN"); - initialiseDnCmb(baseDnCmb); - baseDnCmb.addModifyListener(this); - baseDnCmb.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -1435351236582736843L; - - @Override - public void modifyText(ModifyEvent event) { - String name = usernameTxt.getText(); - dNameTxt.setText(getDn(name)); - } - }); - - usernameTxt = EclipseUiUtils.createGridLT(composite, "Local ID", this); - usernameTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -1435351236582736843L; - - @Override - public void modifyText(ModifyEvent event) { - String name = usernameTxt.getText(); - if (name.trim().equals("")) { - dNameTxt.setText(""); - lastNameTxt.setText(""); - primaryMailTxt.setText(""); - pwd1Txt.setText(""); - pwd2Txt.setText(""); - } else { - dNameTxt.setText(getDn(name)); - lastNameTxt.setText(name.toUpperCase()); - primaryMailTxt.setText(getMail(name)); - pwd1Txt.setText("demo"); - pwd2Txt.setText("demo"); - } - } - }); - - primaryMailTxt = EclipseUiUtils.createGridLT(composite, "Email", this); - firstNameTxt = EclipseUiUtils.createGridLT(composite, "First name", this); - lastNameTxt = EclipseUiUtils.createGridLT(composite, "Last name", this); - pwd1Txt = EclipseUiUtils.createGridLP(composite, "Password", this); - pwd2Txt = EclipseUiUtils.createGridLP(composite, "Repeat password", this); - setControl(composite); - - // Initialize buttons - setPageComplete(false); - getContainer().updateButtons(); - } - - @Override - public void modifyText(ModifyEvent event) { - String message = checkComplete(); - if (message != null) { - setMessage(message, WizardPage.ERROR); - setPageComplete(false); - } else { - setMessage("Complete", WizardPage.INFORMATION); - setPageComplete(true); - } - getContainer().updateButtons(); - } - - /** @return error message or null if complete */ - protected String checkComplete() { - String name = usernameTxt.getText(); - - if (name.trim().equals("")) - return "User name must not be empty"; - Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); - if (role != null) - return "User " + name + " already exists"; - if (!primaryMailTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) - return "Not a valid email address"; - if (lastNameTxt.getText().trim().equals("")) - return "Specify a last name"; - if (pwd1Txt.getText().trim().equals("")) - return "Specify a password"; - if (pwd2Txt.getText().trim().equals("")) - return "Repeat the password"; - if (!pwd2Txt.getText().equals(pwd1Txt.getText())) - return "Passwords are different"; - return null; - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (visible) - if (baseDnCmb.getSelectionIndex() == -1) - baseDnCmb.setFocus(); - else - usernameTxt.setFocus(); - } - - public String getUsername() { - return usernameTxt.getText(); - } - - public char[] getPassword() { - return pwd1Txt.getTextChars(); - } - - } - - private Map getDns() { - return userAdminWrapper.getKnownBaseDns(true); - } - - private String getDn(String uid) { - Map dns = getDns(); - String bdn = baseDnCmb.getText(); - if (EclipseUiUtils.notEmpty(bdn)) { - Dictionary 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 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 rdns = name.getRdns(); - return username + "@" + (String) rdns.get(1).getValue() + '.' + (String) rdns.get(0).getValue(); - } catch (InvalidNameException e) { - throw new CmsException("Unable to generate mail for " + username + " with base dn " + baseDn, e); - } - } - } - - private Combo createGridLC(Composite parent, String label) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); - combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - return combo; - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java deleted file mode 100644 index cf3db1d16..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Users management handlers. */ -package org.argeo.cms.e4.users.handlers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java deleted file mode 100644 index c6f14b0cf..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Users management perspective. */ -package org.argeo.cms.e4.users; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java deleted file mode 100644 index 2d8db67d7..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.service.useradmin.User; - -/** Simply declare a label provider that returns the common name of a user */ -public class CommonNameLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 5256703081044911941L; - - @Override - public String getText(User user) { - return UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); - } - - @Override - public String getToolTipText(Object element) { - return UserAdminUtils.getProperty((User) element, LdapAttrs.DN); - } - -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java deleted file mode 100644 index e23729da8..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.cms.auth.UserAdminUtils; -import org.osgi.service.useradmin.User; - -/** The human friendly domain name for the corresponding user. */ -public class DomainNameLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 5256703081044911941L; - - @Override - public String getText(User user) { - return UserAdminUtils.getDomainName(user); - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java deleted file mode 100644 index 52d3b858f..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.service.useradmin.User; - -/** Simply declare a label provider that returns the Primary Mail of a user */ -public class MailLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 8329764452141982707L; - - @Override - public String getText(User user) { - return UserAdminUtils.getProperty(user, LdapAttrs.mail.name()); - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java deleted file mode 100644 index 8c94093e4..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.SecurityAdminImages; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.swt.graphics.Image; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** Provide a bundle specific image depending on the current user type */ -public class RoleIconLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 6550449442061090388L; - - @Override - public String getText(User user) { - return ""; - } - - @Override - public Image getImage(Object element) { - User user = (User) element; - String dn = user.getName(); - if (dn.endsWith(CmsConstants.ROLES_BASEDN)) - return SecurityAdminImages.ICON_ROLE; - else if (user.getType() == Role.GROUP) { - String businessCategory = UserAdminUtils.getProperty(user, LdapAttrs.businessCategory); - if (businessCategory != null && businessCategory.equals(CmsContext.WORKGROUP)) - return SecurityAdminImages.ICON_WORKGROUP; - return SecurityAdminImages.ICON_GROUP; - } else - return SecurityAdminImages.ICON_USER; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java deleted file mode 100644 index e33b1531d..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.UserAdminUtils; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.User; - -/** - * Utility class that add font modifications to a column label provider - * depending on the given user properties - */ -public abstract class UserAdminAbstractLP extends ColumnLabelProvider { - private static final long serialVersionUID = 137336765024922368L; - - // private Font italic; - private Font bold; - - @Override - public Font getFont(Object element) { - // Self as bold - try { - LdapName selfUserName = UserAdminUtils.getCurrentUserLdapName(); - String userName = ((User) element).getName(); - LdapName userLdapName = new LdapName(userName); - if (userLdapName.equals(selfUserName)) { - if (bold == null) - bold = JFaceResources.getFontRegistry() - .defaultFontDescriptor().setStyle(SWT.BOLD) - .createFont(Display.getCurrent()); - return bold; - } - } catch (InvalidNameException e) { - throw new CmsException("cannot parse dn for " + element, e); - } - - // Disabled as Italic - // Node userProfile = (Node) elem; - // if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) - // return italic; - - return null; - // return super.getFont(element); - } - - @Override - public String getText(Object element) { - User user = (User) element; - return getText(user); - } - - public void setDisplay(Display display) { - // italic = JFaceResources.getFontRegistry().defaultFontDescriptor() - // .setStyle(SWT.ITALIC).createFont(display); - bold = JFaceResources.getFontRegistry().defaultFontDescriptor() - .setStyle(SWT.BOLD).createFont(Display.getCurrent()); - } - - public abstract String getText(User user); -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java deleted file mode 100644 index 56a26244b..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.osgi.service.useradmin.User; - -/** Default drag listener to modify group and users via the UI */ -public class UserDragListener implements DragSourceListener { - private static final long serialVersionUID = -2074337775033781454L; - private final Viewer viewer; - - public UserDragListener(Viewer viewer) { - this.viewer = viewer; - } - - public void dragStart(DragSourceEvent event) { - // TODO implement finer checks - IStructuredSelection selection = (IStructuredSelection) viewer - .getSelection(); - if (selection.isEmpty() || selection.size() > 1) - event.doit = false; - else - event.doit = true; - } - - public void dragSetData(DragSourceEvent event) { - // TODO Support multiple selection - Object obj = ((IStructuredSelection) viewer.getSelection()) - .getFirstElement(); - if (obj != null) { - User user = (User) obj; - event.data = user.getName(); - } - } - - public void dragFinished(DragSourceEvent event) { - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java deleted file mode 100644 index 154b04725..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerFilter; -import org.osgi.service.useradmin.User; - -/** - * Filter user list using JFace mechanism on the client (yet on the server) side - * rather than having the UserAdmin to process the search - */ -public class UserFilter extends ViewerFilter { - private static final long serialVersionUID = 5082509381672880568L; - - private String searchString; - private boolean showSystemRole = true; - - private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.cn.name(), LdapAttrs.givenName.name(), - LdapAttrs.sn.name(), LdapAttrs.uid.name(), LdapAttrs.description.name(), LdapAttrs.mail.name() }; - - public void setSearchText(String s) { - // ensure that the value can be used for matching - if (notEmpty(s)) - searchString = ".*" + s.toLowerCase() + ".*"; - else - searchString = ".*"; - } - - public void setShowSystemRole(boolean showSystemRole) { - this.showSystemRole = showSystemRole; - } - - @Override - public boolean select(Viewer viewer, Object parentElement, Object element) { - User user = (User) element; - if (!showSystemRole && user.getName().matches(".*(" + CmsConstants.ROLES_BASEDN + ")")) - // UserAdminUtils.getProperty(user, LdifName.dn.name()) - // .toLowerCase().endsWith(AuthConstants.ROLES_BASEDN)) - return false; - - if (searchString == null || searchString.length() == 0) - return true; - - if (user.getName().matches(searchString)) - return true; - - for (String key : knownProps) { - String currVal = UserAdminUtils.getProperty(user, key); - if (notEmpty(currVal) && currVal.toLowerCase().matches(searchString)) - return true; - } - return false; - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java deleted file mode 100644 index 3cd00eb2b..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.osgi.service.useradmin.User; - -/** Simply declare a label provider that returns the username of a user */ -public class UserNameLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 6550449442061090388L; - - @Override - public String getText(User user) { - return user.getName(); - } -} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java deleted file mode 100644 index 33bef8dee..000000000 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Users management content providers. */ -package org.argeo.cms.e4.users.providers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.servlet/.classpath b/eclipse/org.argeo.cms.servlet/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/eclipse/org.argeo.cms.servlet/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/eclipse/org.argeo.cms.servlet/.project b/eclipse/org.argeo.cms.servlet/.project deleted file mode 100644 index d39f97472..000000000 --- a/eclipse/org.argeo.cms.servlet/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.servlet - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml deleted file mode 100644 index c007351aa..000000000 --- a/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml deleted file mode 100644 index 00fcaff99..000000000 --- a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml deleted file mode 100644 index 7540a2cdb..000000000 --- a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/eclipse/org.argeo.cms.servlet/bnd.bnd b/eclipse/org.argeo.cms.servlet/bnd.bnd deleted file mode 100644 index 7c537ba6b..000000000 --- a/eclipse/org.argeo.cms.servlet/bnd.bnd +++ /dev/null @@ -1,12 +0,0 @@ -Import-Package:\ -org.osgi.service.http;version=0.0.0,\ -org.osgi.service.http.whiteboard;version=0.0.0,\ -org.osgi.framework.namespace;version=0.0.0,\ -org.argeo.cms.osgi,\ -javax.servlet.*;version="[3,5)",\ -* - -Service-Component:\ -OSGI-INF/jettyServiceFactory.xml,\ -OSGI-INF/pkgServletContext.xml,\ -OSGI-INF/pkgServlet.xml diff --git a/eclipse/org.argeo.cms.servlet/build.properties b/eclipse/org.argeo.cms.servlet/build.properties deleted file mode 100644 index ee94f53be..000000000 --- a/eclipse/org.argeo.cms.servlet/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - OSGI-INF/jettyServiceFactory.xml -source.. = src/ diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java deleted file mode 100644 index 1ae6286ac..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.cms.servlet; - -import java.io.IOException; -import java.net.URL; -import java.security.PrivilegedAction; -import java.util.Map; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.RemoteAuthCallbackHandler; -import org.argeo.cms.auth.RemoteAuthUtils; -import org.argeo.cms.servlet.internal.HttpUtils; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.http.context.ServletContextHelper; - -/** - * Default servlet context degrading to anonymous if the the session is not - * pre-authenticated. - */ -public class CmsServletContext extends ServletContextHelper { - private final static CmsLog log = CmsLog.getLog(CmsServletContext.class); - // use CMS bundle for resources - private Bundle bundle = FrameworkUtil.getBundle(getClass()); - - public void init(Map 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() { - - @Override - public Void run() { - // TODO also set login context in order to log out ? - RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request)); - return null; - } - - }); - return true; - } - - @Override - public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { - RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request)); - } - - protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { - // anonymous - try { - LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, - new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); - lc.login(); - return lc; - } catch (LoginException e1) { - if (log.isDebugEnabled()) - log.error("Cannot log in as anonymous", e1); - return null; - } - } - - @Override - public URL getResource(String name) { - // TODO make it more robust and versatile - // if used directly it can only load from within this bundle - return bundle.getResource(name); - } - -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java deleted file mode 100644 index 3bea0b4de..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.cms.servlet; - -import javax.security.auth.login.LoginContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.cms.auth.SpnegoLoginModule; -import org.argeo.cms.servlet.internal.HttpUtils; - -/** Servlet context forcing authentication. */ -public class PrivateWwwAuthServletContext extends CmsServletContext { - // TODO make it configurable - private final String httpAuthRealm = "Argeo"; - private final boolean forceBasic = false; - - @Override - protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { - askForWwwAuth(request, response); - return null; - } - - protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { - // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic - // realm=\"" + httpAuthRealm + "\""); - if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO - response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate"); - else - response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\""); - - // response.setDateHeader("Date", System.currentTimeMillis()); - // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * - // 60 * 60 * 1000)); - // response.setHeader("Accept-Ranges", "bytes"); - // response.setHeader("Connection", "Keep-Alive"); - // response.setHeader("Keep-Alive", "timeout=5, max=97"); - // response.setContentType("text/html; charset=UTF-8"); - response.setStatus(401); - } -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java deleted file mode 100644 index 54c880435..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.argeo.cms.servlet; - -import java.util.Locale; -import java.util.Objects; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -import org.argeo.cms.auth.RemoteAuthRequest; -import org.argeo.cms.auth.RemoteAuthSession; - -public class ServletHttpRequest implements RemoteAuthRequest { - private final HttpServletRequest request; - - public ServletHttpRequest(HttpServletRequest request) { - Objects.requireNonNull(request); - this.request = request; - } - - @Override - public RemoteAuthSession getSession() { - HttpSession httpSession = request.getSession(false); - if (httpSession == null) - return null; - return new ServletHttpSession(httpSession); - } - - @Override - public RemoteAuthSession createSession() { - return new ServletHttpSession(request.getSession(true)); - } - - @Override - public Locale getLocale() { - return request.getLocale(); - } - - @Override - public Object getAttribute(String key) { - return request.getAttribute(key); - } - - @Override - public void setAttribute(String key, Object object) { - request.setAttribute(key, object); - } - - @Override - public String getHeader(String key) { - return request.getHeader(key); - } - - @Override - public String getRemoteAddr() { - return request.getRemoteAddr(); - } - - @Override - public int getLocalPort() { - return request.getLocalPort(); - } - - @Override - public int getRemotePort() { - return request.getRemotePort(); - } -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java deleted file mode 100644 index de47365ca..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.cms.servlet; - -import java.util.Objects; - -import javax.servlet.http.HttpServletResponse; - -import org.argeo.cms.auth.RemoteAuthResponse; - -public class ServletHttpResponse implements RemoteAuthResponse { - private final HttpServletResponse response; - - public ServletHttpResponse(HttpServletResponse response) { - Objects.requireNonNull(response); - this.response = response; - } - - @Override - public void setHeader(String keys, String value) { - response.setHeader(keys, value); - } - -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java deleted file mode 100644 index 8d087daa7..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.servlet; - -import org.argeo.cms.auth.RemoteAuthSession; - -public class ServletHttpSession implements RemoteAuthSession { - private javax.servlet.http.HttpSession session; - - public ServletHttpSession(javax.servlet.http.HttpSession session) { - super(); - this.session = session; - } - - @Override - public boolean isValid() { - try {// test http session - session.getCreationTime(); - return true; - } catch (IllegalStateException ise) { - return false; - } - } - - @Override - public String getId() { - return session.getId(); - } - -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java deleted file mode 100644 index 70f2cc6b0..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.cms.servlet.internal; - -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsLog; - -public class HttpUtils { - public final static String HEADER_AUTHORIZATION = "Authorization"; - public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - - static boolean isBrowser(String userAgent) { - return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") - || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") - || userAgent.contains("opera") || userAgent.contains("browser"); - } - - public static void logResponseHeaders(CmsLog log, HttpServletResponse response) { - if (!log.isDebugEnabled()) - return; - for (String headerName : response.getHeaderNames()) { - Object headerValue = response.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - } - - public static void logRequestHeaders(CmsLog log, HttpServletRequest request) { - if (!log.isDebugEnabled()) - return; - for (Enumeration 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 en = request.getHeaderNames(); - while (en.hasMoreElements()) { - String header = en.nextElement(); - Enumeration values = request.getHeaders(header); - while (values.hasMoreElements()) - buf.append(" " + header + ": " + values.nextElement()); - buf.append('\n'); - } - - // attributed - Enumeration an = request.getAttributeNames(); - while (an.hasMoreElements()) { - String attr = an.nextElement(); - Object value = request.getAttribute(attr); - buf.append(" " + attr + ": " + value); - buf.append('\n'); - } - log.debug("\n" + buf); - } - - private HttpUtils() { - - } -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java deleted file mode 100644 index c762b67ec..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.cms.servlet.internal; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Collection; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.osgi.PublishNamespace; -import org.argeo.osgi.util.FilterRequirement; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.Version; -import org.osgi.framework.VersionRange; -import org.osgi.framework.namespace.PackageNamespace; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.framework.wiring.FrameworkWiring; -import org.osgi.resource.Requirement; - -public class PkgServlet extends HttpServlet { - private static final long serialVersionUID = 7660824185145214324L; - - private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String pathInfo = req.getPathInfo(); - - String pkg, versionStr, file; - String[] parts = pathInfo.split("/"); - // first is always empty - if (parts.length == 4) { - pkg = parts[1]; - versionStr = parts[2]; - file = parts[3]; - } else if (parts.length == 3) { - pkg = parts[1]; - versionStr = null; - file = parts[2]; - } else { - throw new IllegalArgumentException("Unsupported path length " + pathInfo); - } - - FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); - String filter; - if (versionStr == null) { - filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; - } else { - if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range - VersionRange versionRange = new VersionRange(versionStr); - filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" - + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; - - } else { - Version version = new Version(versionStr); - filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" - + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; - } - } - Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); - Collection packages = frameworkWiring.findProviders(requirement); - if (packages.isEmpty()) { - resp.sendError(404); - return; - } - - // TODO verify that it works with multiple versions - SortedMap sorted = new TreeMap<>(); - for (BundleCapability capability : packages) { - sorted.put(capability.getRevision().getVersion(), capability); - } - - Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); - String entryPath = '/' + pkg.replace('.', '/') + '/' + file; - URL internalURL = bundle.getResource(entryPath); - if (internalURL == null) { - resp.sendError(404); - return; - } - - // Resource found, we now check whether it can be published - boolean publish = false; - BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); - capabilities: for (BundleCapability bundleCapability : bundleWiring - .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { - Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); - if (publishedPkg != null) { - if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { - Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); - if (publishedFile == null) { - publish = true; - break capabilities; - } else { - String[] publishedFiles = publishedFile.toString().split(","); - for (String pattern : publishedFiles) { - if (pattern.startsWith("*.")) { - String ext = pattern.substring(1); - if (file.endsWith(ext)) { - publish = true; - break capabilities; - } - } else { - if (publishedFile.equals(file)) { - publish = true; - break capabilities; - } - } - } - } - } - } - } - - if (!publish) { - resp.sendError(404); - return; - } - - try (InputStream in = internalURL.openStream()) { - IOUtils.copy(in, resp.getOutputStream()); - } - } - -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java deleted file mode 100644 index 288ee268c..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.argeo.cms.servlet.internal; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class RobotServlet extends HttpServlet { - private static final long serialVersionUID = 7935661175336419089L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - PrintWriter writer = response.getWriter(); - writer.append("User-agent: *\n"); - writer.append("Disallow:\n"); - response.setHeader("Content-Type", "text/plain"); - writer.flush(); - } - -} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java deleted file mode 100644 index 05de32c48..000000000 --- a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.argeo.cms.servlet.internal.jetty; - -import java.util.Dictionary; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.eclipse.equinox.http.jetty.JettyConfigurator; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedServiceFactory; - -public class JettyServiceFactory implements ManagedServiceFactory { - private final CmsLog log = CmsLog.getLog(JettyServiceFactory.class); - - public void start() { - - } - - @Override - public String getName() { - return "Jetty Service Factory"; - } - - @Override - public void updated(String pid, Dictionary properties) throws ConfigurationException { - // Explicitly configures Jetty so that the default server is not started by the - // activator of the Equinox Jetty bundle. - -// if (!webServerConfig.isEmpty()) { -// webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS); -// -// // TODO centralise with Jetty extender -// Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED); -// if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { -// bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); -// webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true"); -// } -// } - - int tryCount = 60; - try { - tryGettyJetty: while (tryCount > 0) { - try { - // FIXME deal with multiple ids - JettyConfigurator.startServer(CmsConstants.DEFAULT, properties); - // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi - // configuration is not cleaned - FrameworkUtil.getBundle(JettyConfigurator.class).start(); - break tryGettyJetty; - } catch (IllegalStateException e) { - // Jetty may not be ready - try { - Thread.sleep(1000); - } catch (Exception e1) { - // silent - } - tryCount--; - } - } - } catch (Exception e) { - log.error("Cannot start default Jetty server with config " + properties, e); - } - - } - - @Override - public void deleted(String pid) { - } - - public void stop() { - try { - JettyConfigurator.stopServer(CmsConstants.DEFAULT); - } catch (Exception e) { - log.error("Cannot stop default Jetty server.", e); - } - - } - -} diff --git a/eclipse/org.argeo.cms.swt/.classpath b/eclipse/org.argeo.cms.swt/.classpath deleted file mode 100644 index e03d341b1..000000000 --- a/eclipse/org.argeo.cms.swt/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/eclipse/org.argeo.cms.swt/.project b/eclipse/org.argeo.cms.swt/.project deleted file mode 100644 index 082112e6d..000000000 --- a/eclipse/org.argeo.cms.swt/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.swt - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/eclipse/org.argeo.cms.swt/META-INF/.gitignore b/eclipse/org.argeo.cms.swt/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/eclipse/org.argeo.cms.swt/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/eclipse/org.argeo.cms.swt/bnd.bnd b/eclipse/org.argeo.cms.swt/bnd.bnd deleted file mode 100644 index 29f820a4c..000000000 --- a/eclipse/org.argeo.cms.swt/bnd.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Import-Package: org.eclipse.swt,\ -org.eclipse.jface.window,\ -org.eclipse.core.commands.common,\ -javax.servlet.*;version="[3,5)",\ -* - -Bundle-ActivationPolicy: lazy - \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/build.properties b/eclipse/org.argeo.cms.swt/build.properties deleted file mode 100644 index 0e0438744..000000000 --- a/eclipse/org.argeo.cms.swt/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . - \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/icons/actions/add.png b/eclipse/org.argeo.cms.swt/icons/actions/add.png deleted file mode 100644 index 5c06bf082..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/actions/add.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/close-all.png b/eclipse/org.argeo.cms.swt/icons/actions/close-all.png deleted file mode 100644 index 81bfc950b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/actions/close-all.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/delete.png b/eclipse/org.argeo.cms.swt/icons/actions/delete.png deleted file mode 100644 index 9712723d7..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/actions/delete.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/edit.png b/eclipse/org.argeo.cms.swt/icons/actions/edit.png deleted file mode 100644 index ad3db9f42..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/actions/edit.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save-all.png b/eclipse/org.argeo.cms.swt/icons/actions/save-all.png deleted file mode 100644 index f48ed320b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/actions/save-all.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save.png b/eclipse/org.argeo.cms.swt/icons/actions/save.png deleted file mode 100644 index 1c58ada49..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/actions/save.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/active.gif b/eclipse/org.argeo.cms.swt/icons/active.gif deleted file mode 100644 index 7d24707ee..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/active.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/add.gif b/eclipse/org.argeo.cms.swt/icons/add.gif deleted file mode 100644 index 252d7ebcb..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/add.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/add.png b/eclipse/org.argeo.cms.swt/icons/add.png deleted file mode 100644 index c7edfecaa..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/add.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/addFolder.gif b/eclipse/org.argeo.cms.swt/icons/addFolder.gif deleted file mode 100644 index d3f43d977..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/addFolder.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif b/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif deleted file mode 100644 index a6b251fc8..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/addRepo.gif b/eclipse/org.argeo.cms.swt/icons/addRepo.gif deleted file mode 100644 index 26d81c065..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/addRepo.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/addWorkspace.png b/eclipse/org.argeo.cms.swt/icons/addWorkspace.png deleted file mode 100644 index bbee7755f..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/addWorkspace.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/adminLog.gif b/eclipse/org.argeo.cms.swt/icons/adminLog.gif deleted file mode 100644 index 6ef3bca66..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/adminLog.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/batch.gif b/eclipse/org.argeo.cms.swt/icons/batch.gif deleted file mode 100644 index b8ca14a8b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/batch.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/begin.gif b/eclipse/org.argeo.cms.swt/icons/begin.gif deleted file mode 100755 index feb8e94a7..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/begin.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/binary.png b/eclipse/org.argeo.cms.swt/icons/binary.png deleted file mode 100644 index fdf4f82be..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/binary.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/browser.gif b/eclipse/org.argeo.cms.swt/icons/browser.gif deleted file mode 100644 index 6c7320c69..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/browser.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/bundles.gif b/eclipse/org.argeo.cms.swt/icons/bundles.gif deleted file mode 100644 index e9a6bd966..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/bundles.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/changePassword.gif b/eclipse/org.argeo.cms.swt/icons/changePassword.gif deleted file mode 100644 index 274a850e4..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/changePassword.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/clear.gif b/eclipse/org.argeo.cms.swt/icons/clear.gif deleted file mode 100644 index 6bc10f9d0..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/clear.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/close-all.png b/eclipse/org.argeo.cms.swt/icons/close-all.png deleted file mode 100644 index 85d4d429b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/close-all.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/commit.gif b/eclipse/org.argeo.cms.swt/icons/commit.gif deleted file mode 100755 index 876f3eb16..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/commit.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/delete.png b/eclipse/org.argeo.cms.swt/icons/delete.png deleted file mode 100644 index 676a39dcf..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/delete.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/dumpNode.gif b/eclipse/org.argeo.cms.swt/icons/dumpNode.gif deleted file mode 100644 index 14eb1be09..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/dumpNode.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/file.gif b/eclipse/org.argeo.cms.swt/icons/file.gif deleted file mode 100644 index ef3028807..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/file.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/folder.gif b/eclipse/org.argeo.cms.swt/icons/folder.gif deleted file mode 100644 index 42e027c93..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/folder.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/getSize.gif b/eclipse/org.argeo.cms.swt/icons/getSize.gif deleted file mode 100644 index b05bf3e3d..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/getSize.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/group.png b/eclipse/org.argeo.cms.swt/icons/group.png deleted file mode 100644 index cc6683aff..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/group.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/home.gif b/eclipse/org.argeo.cms.swt/icons/home.gif deleted file mode 100644 index fd0c66950..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/home.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/home.png b/eclipse/org.argeo.cms.swt/icons/home.png deleted file mode 100644 index 5eb096790..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/home.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/import_fs.png b/eclipse/org.argeo.cms.swt/icons/import_fs.png deleted file mode 100644 index d7c890c81..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/import_fs.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/installed.gif b/eclipse/org.argeo.cms.swt/icons/installed.gif deleted file mode 100644 index 298871653..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/installed.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/log.gif b/eclipse/org.argeo.cms.swt/icons/log.gif deleted file mode 100644 index e3ecc5535..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/log.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/logout.png b/eclipse/org.argeo.cms.swt/icons/logout.png deleted file mode 100644 index f2952fa5b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/logout.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/maintenance.gif b/eclipse/org.argeo.cms.swt/icons/maintenance.gif deleted file mode 100644 index e5690ecb1..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/maintenance.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/node.gif b/eclipse/org.argeo.cms.swt/icons/node.gif deleted file mode 100644 index 364c0e70b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/node.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/nodes.gif b/eclipse/org.argeo.cms.swt/icons/nodes.gif deleted file mode 100644 index bba3dbc69..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/nodes.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif b/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif deleted file mode 100644 index e9a6bd966..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/password.gif b/eclipse/org.argeo.cms.swt/icons/password.gif deleted file mode 100644 index a6b251fc8..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/password.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/person-logged-in.png b/eclipse/org.argeo.cms.swt/icons/person-logged-in.png deleted file mode 100644 index 87acc1435..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/person-logged-in.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/person.png b/eclipse/org.argeo.cms.swt/icons/person.png deleted file mode 100644 index 7d979a531..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/person.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/query.png b/eclipse/org.argeo.cms.swt/icons/query.png deleted file mode 100644 index 54c089de1..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/query.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/refresh.png b/eclipse/org.argeo.cms.swt/icons/refresh.png deleted file mode 100644 index 71b3481c9..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/refresh.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/remote_connected.gif b/eclipse/org.argeo.cms.swt/icons/remote_connected.gif deleted file mode 100644 index 1492b4efa..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/remote_connected.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif deleted file mode 100644 index 6c54da9ad..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/remove.gif b/eclipse/org.argeo.cms.swt/icons/remove.gif deleted file mode 100644 index 0ae6decd0..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/remove.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif b/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif deleted file mode 100644 index aa78fd2fa..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/rename.gif b/eclipse/org.argeo.cms.swt/icons/rename.gif deleted file mode 100644 index 8048405a7..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/rename.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/repositories.gif b/eclipse/org.argeo.cms.swt/icons/repositories.gif deleted file mode 100644 index c13bea1ca..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/repositories.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/repository_connected.gif b/eclipse/org.argeo.cms.swt/icons/repository_connected.gif deleted file mode 100644 index a15fa5538..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/repository_connected.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif deleted file mode 100644 index 4576dc563..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/resolved.gif b/eclipse/org.argeo.cms.swt/icons/resolved.gif deleted file mode 100644 index f4a1ea150..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/resolved.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/role.gif b/eclipse/org.argeo.cms.swt/icons/role.gif deleted file mode 100644 index 274a850e4..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/role.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/rollback.gif b/eclipse/org.argeo.cms.swt/icons/rollback.gif deleted file mode 100755 index c75399599..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/rollback.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/save-all.png b/eclipse/org.argeo.cms.swt/icons/save-all.png deleted file mode 100644 index b68a29b2c..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/save-all.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/save.gif b/eclipse/org.argeo.cms.swt/icons/save.gif deleted file mode 100644 index 654ad7b42..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/save.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/save.png b/eclipse/org.argeo.cms.swt/icons/save.png deleted file mode 100644 index f27ef2d26..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/save.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/save_security.png b/eclipse/org.argeo.cms.swt/icons/save_security.png deleted file mode 100644 index ca41dc92b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/save_security.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png b/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png deleted file mode 100644 index fb7d08d9a..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/security.gif b/eclipse/org.argeo.cms.swt/icons/security.gif deleted file mode 100644 index 57fb95edc..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/security.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/service_published.gif b/eclipse/org.argeo.cms.swt/icons/service_published.gif deleted file mode 100644 index 17f771aff..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/service_published.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/service_referenced.gif b/eclipse/org.argeo.cms.swt/icons/service_referenced.gif deleted file mode 100644 index c24a95fba..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/service_referenced.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/sort.gif b/eclipse/org.argeo.cms.swt/icons/sort.gif deleted file mode 100644 index 23c5d0b11..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/sort.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/starting.gif b/eclipse/org.argeo.cms.swt/icons/starting.gif deleted file mode 100644 index 563743d39..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/starting.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/sync.gif b/eclipse/org.argeo.cms.swt/icons/sync.gif deleted file mode 100644 index b4fa052de..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/sync.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/user.gif b/eclipse/org.argeo.cms.swt/icons/user.gif deleted file mode 100644 index 90a00147b..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/user.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/users.gif b/eclipse/org.argeo.cms.swt/icons/users.gif deleted file mode 100644 index 2de7edd64..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/users.gif and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.png b/eclipse/org.argeo.cms.swt/icons/workgroup.png deleted file mode 100644 index 7fef996df..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/workgroup.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.xcf b/eclipse/org.argeo.cms.swt/icons/workgroup.xcf deleted file mode 100644 index f517c827c..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/workgroup.xcf and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_connected.png b/eclipse/org.argeo.cms.swt/icons/workspace_connected.png deleted file mode 100644 index 0430baaf5..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/workspace_connected.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png b/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png deleted file mode 100644 index fddcb8c4e..000000000 Binary files a/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java deleted file mode 100644 index 4ff89f27a..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.argeo.cms.swt; - -import org.argeo.api.cms.CmsTheme; -import org.eclipse.swt.graphics.Image; - -/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */ -public interface CmsIcon { - String name(); - - default Image getSmallIcon(CmsTheme theme) { - return ((CmsSwtTheme) theme).getIcon(name(), getSmallIconSize()); - } - - default Image getBigIcon(CmsTheme theme) { - return ((CmsSwtTheme) theme).getIcon(name(), getBigIconSize()); - } - - default Integer getSmallIconSize() { - return 16; - } - - default Integer getBigIconSize() { - return 32; - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java deleted file mode 100644 index 9eba6f6ec..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms.swt; - -/** Styles references in the CSS. */ -@Deprecated -public interface CmsStyles { - // General - public final static String CMS_SHELL = "cms_shell"; - public final static String CMS_MENU_LINK = "cms_menu_link"; - - // Header - public final static String CMS_HEADER = "cms_header"; - public final static String CMS_HEADER_LEAD = "cms_header-lead"; - public final static String CMS_HEADER_CENTER = "cms_header-center"; - public final static String CMS_HEADER_END = "cms_header-end"; - - public final static String CMS_LEAD = "cms_lead"; - public final static String CMS_END = "cms_end"; - public final static String CMS_FOOTER = "cms_footer"; - - public final static String CMS_USER_MENU = "cms_user_menu"; - public final static String CMS_USER_MENU_LINK = "cms_user_menu-link"; - public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item"; - public final static String CMS_LOGIN_DIALOG = "cms_login_dialog"; - public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username"; - public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password"; - - // Body - public final static String CMS_SCROLLED_AREA = "cms_scrolled_area"; - public final static String CMS_BODY = "cms_body"; - public final static String CMS_STATIC_TEXT = "cms_static-text"; - public final static String CMS_LINK = "cms_link"; -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java deleted file mode 100644 index b5f7c0e4d..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.swt; - -import org.argeo.api.cms.CmsTheme; -import org.eclipse.swt.graphics.Image; - -/** SWT specific {@link CmsTheme}. */ -public interface CmsSwtTheme extends CmsTheme { - /** The image registered at this path, or null 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 null if not found. - * - * @param name An icon file name without path and extension. - * @param preferredSize the preferred size, if null, - * {@link #getDefaultIconSize()} will be tried. - */ - Image getIcon(String name, Integer preferredSize); - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java deleted file mode 100644 index 701de2827..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.argeo.cms.swt; - -import java.util.HashMap; -import java.util.Map; - -import org.argeo.api.cms.CmsStyle; -import org.argeo.api.cms.CmsTheme; -import org.argeo.api.cms.CmsView; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.Widget; - -/** SWT utilities. */ -public class CmsSwtUtils { - - /** Singleton. */ - private CmsSwtUtils() { - } - - public static CmsTheme getCmsTheme(Composite parent) { - CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName()); - if (theme == null) { - // find parent shell - Shell topShell = parent.getShell(); - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - theme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); - parent.setData(CmsTheme.class.getName(), theme); - } - return theme; - } - - public static void registerCmsTheme(Shell shell, CmsTheme theme) { - // find parent shell - Shell topShell = shell; - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - // check if already set - if (topShell.getData(CmsTheme.class.getName()) != null) { - CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); - throw new IllegalArgumentException( - "Theme " + registeredTheme.getThemeId() + " already registered in this shell"); - } - topShell.setData(CmsTheme.class.getName(), theme); - } - - public static CmsView getCmsView(Control parent) { - // find parent shell - Shell topShell = parent.getShell(); - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - return (CmsView) topShell.getData(CmsView.class.getName()); - } - - public static void registerCmsView(Shell shell, CmsView view) { - // find parent shell - Shell topShell = shell; - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - // check if already set - if (topShell.getData(CmsView.class.getName()) != null) { - CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName()); - throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell"); - } - shell.setData(CmsView.class.getName(), view); - } - - /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */ - public static void sendEventOnSelect(Control control, String topic, Map 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 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 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 style(T widget, CmsStyle style) { - return style(widget, style.style()); - } - - /** Enable markups on widget */ - public static T markup(T widget) { - EclipseUiSpecificUtils.setMarkupData(widget); - return widget; - } - - /** Disable markup validation. */ - public static 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 toString() method. - * This argument should not be null, but if it is null and - * assertions are disabled "" is displayed instead; if - * assertions are enabled the call will fail. - * - * @see markup - */ - public static T text(T widget, Object txt) { - assert txt != null; - String str = txt != null ? txt.toString() : ""; - markup(widget); - if (widget instanceof Label) - ((Label) widget).setText(str); - else if (widget instanceof Button) - ((Button) widget).setText(str); - else if (widget instanceof Text) - ((Text) widget).setText(str); - else - throw new IllegalArgumentException("Unsupported widget type " + widget.getClass()); - return widget; - } - - /** A {@link Label} with markup activated. */ - public static Label lbl(Composite parent, Object txt) { - return text(new Label(parent, SWT.NONE), txt); - } - - /** A read-only {@link Text} whose content can be copy/pasted. */ - public static Text txt(Composite parent, Object txt) { - return text(new Text(parent, SWT.NONE), txt); - } - - /** Dispose all children of a Composite */ - public static void clear(Composite composite) { - if (composite.isDisposed()) - return; - for (Control child : composite.getChildren()) - child.dispose(); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java deleted file mode 100644 index b818b06d9..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.swt; - -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; - -/** - * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface - * in order to use as a short lambda expression in UI code. - * {@link MouseListener#mouseDownouseEvent)} and - * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. - */ -@FunctionalInterface -public interface MouseDoubleClick extends MouseListener { - @Override - void mouseDoubleClick(MouseEvent e); - - @Override - default void mouseDown(MouseEvent e) { - // does nothing - } - - @Override - default void mouseUp(MouseEvent e) { - // does nothing - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java deleted file mode 100644 index baecb0072..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.swt; - -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; - -/** - * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in - * order to use as a short lambda expression in UI code. - * {@link MouseListener#mouseDoubleClick(MouseEvent)} and - * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. - */ -@FunctionalInterface -public interface MouseDown extends MouseListener { - @Override - void mouseDown(MouseEvent e); - - @Override - default void mouseDoubleClick(MouseEvent e) { - // does nothing - } - - @Override - default void mouseUp(MouseEvent e) { - // does nothing - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java deleted file mode 100644 index 03fbad01e..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.swt; - -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; - -/** - * {@link SelectionListener} as a functional interface in order to use as a - * short lambda expression in UI code. - * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing - * by default. - */ -@FunctionalInterface -public interface Selected extends SelectionListener { - @Override - public void widgetSelected(SelectionEvent e); - - default public void widgetDefaultSelected(SelectionEvent e) { - // does nothing - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java deleted file mode 100644 index 9c55e8b10..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.argeo.cms.swt; - -import org.argeo.api.cms.UxContext; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Display; - -public class SimpleSwtUxContext implements UxContext { - private Point size; - private Point small = new Point(400, 400); - - public SimpleSwtUxContext() { - this(Display.getCurrent().getBounds()); - } - - public SimpleSwtUxContext(Rectangle rect) { - this.size = new Point(rect.width, rect.height); - } - - public SimpleSwtUxContext(Point size) { - this.size = size; - } - - @Override - public boolean isPortrait() { - return size.x >= size.y; - } - - @Override - public boolean isLandscape() { - return size.x < size.y; - } - - @Override - public boolean isSquare() { - return size.x == size.y; - } - - @Override - public boolean isSmall() { - return size.x <= small.x || size.y <= small.y; - } - - @Override - public boolean isMasterData() { - // TODO make it configurable - return true; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java deleted file mode 100644 index 43e57396b..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java +++ /dev/null @@ -1,337 +0,0 @@ -package org.argeo.cms.swt.auth; - -import static org.argeo.cms.CmsMsg.password; -import static org.argeo.cms.CmsMsg.username; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.LanguageCallback; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsView; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.LocaleUtils; -import org.argeo.cms.auth.RemoteAuthCallback; -import org.argeo.cms.servlet.ServletHttpRequest; -import org.argeo.cms.servlet.ServletHttpResponse; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.specific.UiContext; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -public class CmsLogin implements CmsStyles, CallbackHandler { - private final static CmsLog log = CmsLog.getLog(CmsLogin.class); - - private Composite parent; - private Text usernameT, passwordT; - private Composite credentialsBlock; - private final SelectionListener loginSelectionListener; - - private final Locale defaultLocale; - private LocaleChoice localeChoice = null; - - private final CmsView cmsView; - - // optional subject to be set explicitly - private Subject subject = null; - - private CmsContext cmsContext; - - public CmsLogin(CmsView cmsView, CmsContext cmsContext) { - this.cmsView = cmsView; - this.cmsContext = cmsContext; - if (this.cmsContext != null) { - defaultLocale = this.cmsContext.getDefaultLocale(); - List 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 locales = localeChoice.getLocales(); - for (Integer i = 0; i < locales.size(); i++) { - Locale locale = locales.get(i); - Button button = new Button(c, SWT.RADIO); - CmsSwtUtils.style(button, CMS_USER_MENU_ITEM); - button.setData(i); - button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")"); - // button.addListener(SWT.Selection, listener); - button.addSelectionListener(selectionListener); - if (i == localeChoice.getSelectedIndex()) - button.setSelection(true); - } - return c; - } - - protected boolean login() { - // TODO use CmsVie in order to retrieve subject? - // Subject subject = cmsView.getLoginContext().getSubject(); - // LoginContext loginContext = cmsView.getLoginContext(); - try { - // - // LOGIN - // - // loginContext.logout(); - LoginContext loginContext; - if (subject == null) - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this); - else - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this); - loginContext.login(); - cmsView.authChange(loginContext); - return true; - } catch (LoginException e) { - if (log.isTraceEnabled()) - log.warn("Login failed: " + e.getMessage(), e); - else - log.warn("Login failed: " + e.getMessage()); - - try { - Thread.sleep(3000); - } catch (InterruptedException e2) { - // silent - } - // ErrorFeedback.show("Login failed", e); - return false; - } - // catch (LoginException e) { - // log.error("Cannot login", e); - // return false; - // } - } - - protected void logout() { - cmsView.logout(); - cmsView.navigateTo("~"); - } - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback && usernameT != null) - ((NameCallback) callback).setName(usernameT.getText()); - else if (callback instanceof PasswordCallback && passwordT != null) - ((PasswordCallback) callback).setPassword(passwordT.getTextChars()); - else if (callback instanceof RemoteAuthCallback) { - ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); - ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); - } else if (callback instanceof LanguageCallback) { - Locale toUse = null; - if (localeChoice != null) - toUse = localeChoice.getSelectedLocale(); - else if (defaultLocale != null) - toUse = defaultLocale; - - if (toUse != null) { - ((LanguageCallback) callback).setLocale(toUse); - UiContext.setLocale(toUse); - } - - } - } - } - - public void setSubject(Subject subject) { - this.subject = subject; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java deleted file mode 100644 index a4d7c0770..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.swt.auth; - -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsView; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** The site-related user menu */ -public class CmsLoginShell extends CmsLogin { - private final Shell shell; - - public CmsLoginShell(CmsView cmsView, CmsContext cmsContext) { - super(cmsView, cmsContext); - shell = createShell(); -// createUi(shell); - } - - /** To be overridden. */ - protected Shell createShell() { - Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM); - shell.setMaximized(true); - return shell; - } - - /** To be overridden. */ - public void open() { - CmsSwtUtils.style(shell, CMS_USER_MENU); - shell.open(); - } - - @Override - protected boolean login() { - boolean success = false; - try { - success = super.login(); - return success; - } finally { - if (success) - closeShell(); - else { - for (Control child : shell.getChildren()) - child.dispose(); - createUi(shell); - shell.layout(); - // TODO error message - } - } - } - - @Override - protected void logout() { - closeShell(); - super.logout(); - } - - protected void closeShell() { - if (!shell.isDisposed()) { - shell.close(); - shell.dispose(); - } - } - - public Shell getShell() { - return shell; - } - - public void createUi() { - createUi(shell); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java deleted file mode 100644 index 495007cb2..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java +++ /dev/null @@ -1,273 +0,0 @@ -package org.argeo.cms.swt.auth; - -import java.io.IOException; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** - * A composite that can populate itself based on {@link Callback}s. It can be - * used directly as a {@link CallbackHandler} or be used by one by calling the - * {@link #createCallbackHandlers(Callback[])}. Supported standard - * {@link Callback}s are:
- *
    - *
  • {@link PasswordCallback}
  • - *
  • {@link NameCallback}
  • - *
  • {@link TextOutputCallback}
  • - *
- * Supported Argeo {@link Callback}s are:
- *
    - *
  • {@link LocaleChoice}
  • - *
- */ -public class CompositeCallbackHandler extends Composite implements CallbackHandler { - private static final long serialVersionUID = -928223893722723777L; - - private boolean wasUsedAlready = false; - private boolean isSubmitted = false; - private boolean isCanceled = false; - - public CompositeCallbackHandler(Composite parent, int style) { - super(parent, style); - } - - @Override - public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { - // reset - if (wasUsedAlready && !isSubmitted() && !isCanceled()) { - cancel(); - for (Control control : getChildren()) - control.dispose(); - isSubmitted = false; - isCanceled = false; - } - - for (Callback callback : callbacks) - checkCallbackSupported(callback); - // create controls synchronously in the UI thread - getDisplay().syncExec(new Runnable() { - - @Override - public void run() { - createCallbackHandlers(callbacks); - } - }); - - if (!wasUsedAlready) - wasUsedAlready = true; - - // while (!isSubmitted() && !isCanceled()) { - // try { - // wait(1000l); - // } catch (InterruptedException e) { - // // silent - // } - // } - - // cleanCallbacksAfterCancel(callbacks); - } - - public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException { - if (callback instanceof TextOutputCallback || callback instanceof NameCallback - || callback instanceof PasswordCallback || callback instanceof LocaleChoice) { - return; - } else { - throw new UnsupportedCallbackException(callback); - } - } - - /** - * Set writable callbacks to null if the handle is canceled (check is done - * by the method) - */ - public void cleanCallbacksAfterCancel(Callback[] callbacks) { - if (isCanceled()) { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(null); - } else if (callback instanceof PasswordCallback) { - PasswordCallback pCallback = (PasswordCallback) callback; - char[] arr = pCallback.getPassword(); - if (arr != null) { - Arrays.fill(arr, '*'); - pCallback.setPassword(null); - } - } - } - } - } - - public void createCallbackHandlers(Callback[] callbacks) { - Composite composite = this; - for (int i = 0; i < callbacks.length; i++) { - Callback callback = callbacks[i]; - if (callback instanceof TextOutputCallback) { - createLabelTextoutputHandler(composite, (TextOutputCallback) callback); - } else if (callback instanceof NameCallback) { - createNameHandler(composite, (NameCallback) callback); - } else if (callback instanceof PasswordCallback) { - createPasswordHandler(composite, (PasswordCallback) callback); - } else if (callback instanceof LocaleChoice) { - createLocaleHandler(composite, (LocaleChoice) callback); - } - } - } - - protected Text createNameHandler(Composite composite, final NameCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER); - if (callback.getDefaultName() != null) { - // set default value, if provided - text.setText(callback.getDefaultName()); - callback.setName(callback.getDefaultName()); - } - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 7300032545287292973L; - - public void modifyText(ModifyEvent event) { - callback.setName(text.getText()); - } - }); - text.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - - text.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8698107785092095713L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - } - }); - return text; - } - - protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); - passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - passwordText.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -7099363995047686732L; - - public void modifyText(ModifyEvent event) { - callback.setPassword(passwordText.getTextChars()); - } - }); - passwordText.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - return passwordText; - } - - protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) { - String[] labels = callback.getSupportedLocalesLabels(); - if (labels.length == 0) - return null; - Label label = new Label(composite, SWT.NONE); - label.setText("Language"); - - final Combo combo = new Combo(composite, SWT.READ_ONLY); - combo.setItems(labels); - combo.select(callback.getDefaultIndex()); - combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - combo.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 38678989091946277L; - - @Override - public void widgetSelected(SelectionEvent e) { - callback.setSelectedIndex(combo.getSelectionIndex()); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - }); - return combo; - } - - protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getMessage()); - GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); - data.horizontalSpan = 2; - label.setLayoutData(data); - return label; - // TODO: find a way to pass this information - // int messageType = callback.getMessageType(); - // int dialogMessageType = IMessageProvider.NONE; - // switch (messageType) { - // case TextOutputCallback.INFORMATION: - // dialogMessageType = IMessageProvider.INFORMATION; - // break; - // case TextOutputCallback.WARNING: - // dialogMessageType = IMessageProvider.WARNING; - // break; - // case TextOutputCallback.ERROR: - // dialogMessageType = IMessageProvider.ERROR; - // break; - // } - // setMessage(callback.getMessage(), dialogMessageType); - } - - synchronized boolean isSubmitted() { - return isSubmitted; - } - - synchronized boolean isCanceled() { - return isCanceled; - } - - protected synchronized void submit() { - isSubmitted = true; - notifyAll(); - } - - protected synchronized void cancel() { - isCanceled = true; - notifyAll(); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java deleted file mode 100644 index b0c36c602..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.cms.swt.auth; - -import java.io.IOException; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.argeo.eclipse.ui.dialogs.LightweightDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -public class DynamicCallbackHandler implements CallbackHandler { - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - Shell activeShell = Display.getCurrent().getActiveShell(); - LightweightDialog dialog = new LightweightDialog(activeShell) { - - @Override - protected Control createDialogArea(Composite parent) { - CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE); - cch.createCallbackHandlers(callbacks); - return cch; - } - }; - dialog.setBlockOnOpen(true); - dialog.open(); - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java deleted file mode 100644 index e98e390ee..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.argeo.cms.swt.auth; - -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import javax.security.auth.callback.LanguageCallback; - -import org.argeo.cms.CmsException; -import org.argeo.cms.LocaleUtils; - -/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ -public class LocaleChoice { - private final List locales; - - private Integer selectedIndex = null; - private final Integer defaultIndex; - - public LocaleChoice(List 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 getLocales() { - return locales; - } - - public Locale getDefaultLocale() { - return locales.get(getDefaultIndex()); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java deleted file mode 100644 index b431423d8..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS authentication widgets, based on SWT. */ -package org.argeo.cms.swt.auth; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java deleted file mode 100644 index 8ff086283..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import java.security.PrivilegedAction; -import java.util.Arrays; - -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.CmsUserManager; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** Dialog to change a password. */ -public class ChangePasswordDialog extends CmsMessageDialog { - private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class); - - private CmsUserManager cmsUserManager; - private CmsView cmsView; - - private PrivilegedAction doIt; - - public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) { - super(parentShell, message, kind); - this.cmsUserManager = cmsUserManager; - cmsView = CmsSwtUtils.getCmsView(parentShell); - } - - @Override - protected Control createInputArea(Composite userSection) { - addFormLabel(userSection, CmsMsg.currentPassword.lead()); - Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); - previousPassword.setLayoutData(CmsSwtUtils.fillWidth()); - addFormLabel(userSection, CmsMsg.newPassword.lead()); - Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); - newPassword.setLayoutData(CmsSwtUtils.fillWidth()); - addFormLabel(userSection, CmsMsg.repeatNewPassword.lead()); - Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); - confirmPassword.setLayoutData(CmsSwtUtils.fillWidth()); - - doIt = () -> { - if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) { - try { - cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars()); - return OK; - } catch (Exception e1) { - log.error("Could not change password", e1); - cancel(); - CmsMessageDialog.openError(CmsMsg.invalidPassword.lead()); - return CANCEL; - } - } else { - cancel(); - CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead()); - return CANCEL; - } - }; - - pack(); - return previousPassword; - } - - @Override - protected void okPressed() { - Integer returnCode = cmsView.doAs(doIt); - if (returnCode.equals(OK)) { - super.okPressed(); - CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead()); - } - } - - private static Label addFormLabel(Composite parent, String label) { - Label lbl = new Label(parent, SWT.WRAP); - lbl.setText(label); -// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel); - return lbl; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java deleted file mode 100644 index a01c919e9..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.swt.Selected; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** A dialog feedback based on a {@link LightweightDialog}. */ -public class CmsFeedback extends LightweightDialog { - private final static CmsLog log = CmsLog.getLog(CmsFeedback.class); - - private String message; - private Throwable exception; - - public CmsFeedback(Shell parentShell, String message, Throwable e) { - super(parentShell); - this.message = message; - this.exception = e; - log.error(message, e); - } - - public static CmsFeedback show(String message, Throwable e) { - // rethrow ThreaDeath in order to make sure that RAP will properly clean - // up the UI thread - if (e instanceof ThreadDeath) - throw (ThreadDeath) e; - - try { - CmsFeedback cmsFeedback = new CmsFeedback(null, message, e); - cmsFeedback.setBlockOnOpen(false); - cmsFeedback.open(); - return cmsFeedback; - } catch (Throwable e1) { - log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e); - return null; - } - } - - public static CmsFeedback show(String message) { - CmsFeedback cmsFeedback = new CmsFeedback(null, message, null); - cmsFeedback.open(); - return cmsFeedback; - } - - /** Tries to find a display */ - // private static Display getDisplay() { - // try { - // Display display = Display.getCurrent(); - // if (display != null) - // return display; - // else - // return Display.getDefault(); - // } catch (Exception e) { - // return Display.getCurrent(); - // } - // } - - protected Control createDialogArea(Composite parent) { - parent.setLayout(new GridLayout(2, false)); - - Label messageLbl = new Label(parent, SWT.WRAP); - if (message != null) - messageLbl.setText(message); - else if (exception != null) - messageLbl.setText(exception.getLocalizedMessage()); - - Button close = new Button(parent, SWT.FLAT); - close.setText(CmsMsg.close.lead()); - close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false)); - close.addSelectionListener((Selected) (e) -> closeShell(OK)); - - // Composite composite = new Composite(dialogarea, SWT.NONE); - // composite.setLayout(new GridLayout(2, false)); - // composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - if (exception != null) { - Text stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - stack.setEditable(false); - stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - stack.setText(sw.toString()); - } - - // parent.pack(); - return messageLbl; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java deleted file mode 100644 index 66e640595..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.Selected; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Base class for dialogs displaying messages or small forms. */ -public class CmsMessageDialog extends LightweightDialog { - public final static int NONE = 0; - public final static int ERROR = 1; - public final static int INFORMATION = 2; - public final static int QUESTION = 3; - public final static int WARNING = 4; - public final static int CONFIRM = 5; - public final static int QUESTION_WITH_CANCEL = 6; - - private int kind; - private String message; - - public CmsMessageDialog(Shell parentShell, String message, int kind) { - super(parentShell); - this.kind = kind; - this.message = message; - } - - protected Control createDialogArea(Composite parent) { - parent.setLayout(new GridLayout()); - - TraverseListener traverseListener = new TraverseListener() { - private static final long serialVersionUID = -1158892811534971856L; - - public void keyTraversed(TraverseEvent e) { - if (e.detail == SWT.TRAVERSE_RETURN) - okPressed(); - else if (e.detail == SWT.TRAVERSE_ESCAPE) - cancelPressed(); - } - }; - - // message - Composite body = new Composite(parent, SWT.NONE); - body.addTraverseListener(traverseListener); - GridLayout bodyGridLayout = new GridLayout(); - bodyGridLayout.marginHeight = 20; - bodyGridLayout.marginWidth = 20; - body.setLayout(bodyGridLayout); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - if (message != null) { - Label messageLbl = new Label(body, SWT.WRAP); - CmsSwtUtils.markup(messageLbl); - messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - messageLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - messageLbl.setText(message); - } - - // buttons - Composite buttons = new Composite(parent, SWT.NONE); - buttons.addTraverseListener(traverseListener); - buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) { - GridLayout layout = new GridLayout(1, true); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttons.setLayout(layout); - - Button close = new Button(buttons, SWT.FLAT); - close.setText(CmsMsg.close.lead()); - close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - close.addSelectionListener((Selected) (e) -> closeShell(OK)); - close.setFocus(); - close.addTraverseListener(traverseListener); - - buttons.setTabList(new Control[] { close }); - } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) { - Control input = createInputArea(body); - if (input != null) { - input.addTraverseListener(traverseListener); - body.setTabList(new Control[] { input }); - } - GridLayout layout = new GridLayout(2, true); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttons.setLayout(layout); - - Button cancel = new Button(buttons, SWT.FLAT); - cancel.setText(CmsMsg.cancel.lead()); - cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - cancel.addSelectionListener((Selected) (e) -> cancelPressed()); - cancel.addTraverseListener(traverseListener); - - Button ok = new Button(buttons, SWT.FLAT); - ok.setText(CmsMsg.ok.lead()); - ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - ok.addSelectionListener((Selected) (e) -> okPressed()); - ok.addTraverseListener(traverseListener); - if (input == null) - ok.setFocus(); - else - input.setFocus(); - - buttons.setTabList(new Control[] { ok, cancel }); - } - // pack(); - parent.setTabList(new Control[] { body, buttons }); - return body; - } - - protected Control createInputArea(Composite parent) { - return null; - } - - protected void okPressed() { - closeShell(OK); - } - - protected void cancelPressed() { - closeShell(CANCEL); - } - - protected void cancel() { - closeShell(CANCEL); - } - - protected Point getInitialSize() { - return new Point(400, 200); - } - - public static boolean open(int kind, Shell parent, String message) { - CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind); - return dialog.open() == 0; - } - - public static boolean openConfirm(String message) { - return open(CONFIRM, Display.getCurrent().getActiveShell(), message); - } - - public static void openInformation(String message) { - open(INFORMATION, Display.getCurrent().getActiveShell(), message); - } - - public static boolean openQuestion(String message) { - return open(QUESTION, Display.getCurrent().getActiveShell(), message); - } - - public static void openWarning(String message) { - open(WARNING, Display.getCurrent().getActiveShell(), message); - } - - public static void openError(String message) { - open(ERROR, Display.getCurrent().getActiveShell(), message); - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java deleted file mode 100644 index 59d9ab7f5..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import java.lang.reflect.InvocationTargetException; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.Selected; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.wizard.IWizard; -import org.eclipse.jface.wizard.IWizardContainer2; -import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.FormLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** A wizard dialog based on {@link LightweightDialog}. */ -public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 { - private static final long serialVersionUID = -2123153353654812154L; - - private IWizard wizard; - private IWizardPage currentPage; - private int currentPageIndex; - - private Label titleBar; - private Label message; - private Composite[] pageBodies; - private Composite buttons; - private Button back; - private Button next; - private Button finish; - - public CmsWizardDialog(Shell parentShell, IWizard wizard) { - super(parentShell); - this.wizard = wizard; - wizard.setContainer(this); - // create the pages - wizard.addPages(); - currentPage = wizard.getStartingPage(); - if (currentPage == null) - throw new IllegalArgumentException("At least one wizard page is required"); - } - - @Override - protected Control createDialogArea(Composite parent) { - updateWindowTitle(); - - Composite messageArea = new Composite(parent, SWT.NONE); - messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - { - messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); - titleBar = new Label(messageArea, SWT.WRAP); - titleBar.setFont(EclipseUiUtils.getBoldFont(parent)); - titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false)); - updateTitleBar(); - Button cancelButton = new Button(messageArea, SWT.FLAT); - cancelButton.setText(CmsMsg.cancel.lead()); - cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3)); - cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL)); - message = new Label(messageArea, SWT.WRAP); - message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2)); - updateMessage(); - } - - Composite body = new Composite(parent, SWT.BORDER); - body.setLayout(new FormLayout()); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - pageBodies = new Composite[wizard.getPageCount()]; - IWizardPage[] pages = wizard.getPages(); - for (int i = 0; i < pages.length; i++) { - pageBodies[i] = new Composite(body, SWT.NONE); - pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout()); - setSwitchingFormData(pageBodies[i]); - pages[i].createControl(pageBodies[i]); - } - showPage(currentPage); - - buttons = new Composite(parent, SWT.NONE); - buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - { - boolean singlePage = wizard.getPageCount() == 1; - // singlePage = false;// dev - GridLayout layout = new GridLayout(singlePage ? 1 : 3, true); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttons.setLayout(layout); - // TODO revert order for right-to-left languages - - if (!singlePage) { - back = new Button(buttons, SWT.PUSH); - back.setText(CmsMsg.wizardBack.lead()); - back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - back.addSelectionListener((Selected) (e) -> backPressed()); - - next = new Button(buttons, SWT.PUSH); - next.setText(CmsMsg.wizardNext.lead()); - next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - next.addSelectionListener((Selected) (e) -> nextPressed()); - } - finish = new Button(buttons, SWT.PUSH); - finish.setText(CmsMsg.wizardFinish.lead()); - finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - finish.addSelectionListener((Selected) (e) -> finishPressed()); - - updateButtons(); - } - return body; - } - - @Override - public IWizardPage getCurrentPage() { - return currentPage; - } - - @Override - public Shell getShell() { - return getForegoundShell(); - } - - @Override - public void showPage(IWizardPage page) { - IWizardPage[] pages = wizard.getPages(); - int index = -1; - for (int i = 0; i < pages.length; i++) { - if (page == pages[i]) { - index = i; - break; - } - } - if (index < 0) - throw new IllegalArgumentException("Cannot find index of wizard page " + page); - pageBodies[index].moveAbove(pageBodies[currentPageIndex]); - - // // clear - // for (Control c : body.getChildren()) - // c.dispose(); - // page.createControl(body); - // body.layout(true, true); - currentPageIndex = index; - currentPage = page; - } - - @Override - public void updateButtons() { - if (back != null) - back.setEnabled(wizard.getPreviousPage(currentPage) != null); - if (next != null) - next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage()); - if (finish != null) { - finish.setEnabled(wizard.canFinish()); - } - } - - @Override - public void updateMessage() { - if (currentPage.getMessage() != null) - message.setText(currentPage.getMessage()); - } - - @Override - public void updateTitleBar() { - if (currentPage.getTitle() != null) - titleBar.setText(currentPage.getTitle()); - } - - @Override - public void updateWindowTitle() { - setTitle(wizard.getWindowTitle()); - } - - @Override - public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) - throws InvocationTargetException, InterruptedException { - runnable.run(null); - } - - @Override - public void updateSize() { - // TODO pack? - } - - protected boolean onCancel() { - return wizard.performCancel(); - } - - protected void nextPressed() { - IWizardPage page = wizard.getNextPage(currentPage); - showPage(page); - updateButtons(); - } - - protected void backPressed() { - IWizardPage page = wizard.getPreviousPage(currentPage); - showPage(page); - updateButtons(); - } - - protected void finishPressed() { - if (wizard.performFinish()) - closeShell(OK); - } - - private static void setSwitchingFormData(Composite composite) { - FormData fdLabel = new FormData(); - fdLabel.top = new FormAttachment(0, 0); - fdLabel.left = new FormAttachment(0, 0); - fdLabel.right = new FormAttachment(100, 0); - fdLabel.bottom = new FormAttachment(100, 0); - composite.setLayoutData(fdLabel); - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java deleted file mode 100644 index bf6417bea..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java +++ /dev/null @@ -1,255 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** Generic lightweight dialog, not based on JFace. */ -public class LightweightDialog { - private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); - - // must be the same value as org.eclipse.jface.window.Window#OK - public final static int OK = 0; - // must be the same value as org.eclipse.jface.window.Window#CANCEL - public final static int CANCEL = 1; - - private Shell parentShell; - private Shell backgroundShell; - private Shell foregoundShell; - - private Integer returnCode = null; - private boolean block = true; - - private String title; - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public LightweightDialog(Shell parentShell) { - this.parentShell = parentShell; - } - - public int open() { - if (foregoundShell != null) - throw new EclipseUiException("There is already a shell"); - backgroundShell = new Shell(parentShell, SWT.ON_TOP); - backgroundShell.setFullScreen(true); - // if (parentShell != null) { - // backgroundShell.setBounds(parentShell.getBounds()); - // } else - // backgroundShell.setMaximized(true); - backgroundShell.setAlpha(128); - backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); - foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); - if (title != null) - setTitle(title); - foregoundShell.setLayout(new GridLayout()); - foregoundShell.setSize(getInitialSize()); - createDialogArea(foregoundShell); - // shell.pack(); - // shell.layout(); - - Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP - Point dialogSize = foregoundShell.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - foregoundShell.setLocation(x, y); - - foregoundShell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = -2701270481953688763L; - - @Override - public void shellDeactivated(ShellEvent e) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - - @Override - public void shellClosed(ShellEvent e) { - notifyClose(); - } - - }); - - backgroundShell.open(); - foregoundShell.open(); - // after the foreground shell has been opened - backgroundShell.addFocusListener(new FocusListener() { - private static final long serialVersionUID = 3137408447474661070L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - }); - - if (block) { - block(); - } - if (returnCode == null) - returnCode = OK; - return returnCode; - } - - public void block() { - try { - runEventLoop(foregoundShell); - } catch (ThreadDeath t) { - returnCode = CANCEL; - if (log.isTraceEnabled()) - log.error("Thread death, canceling dialog", t); - } catch (Throwable t) { - returnCode = CANCEL; - log.error("Cannot open blocking lightweight dialog", t); - } - } - - private boolean hasChildShells() { - if (foregoundShell == null) - return false; - return foregoundShell.getShells().length != 0; - } - - // public synchronized int openAndWait() { - // open(); - // while (returnCode == null) - // try { - // wait(100); - // } catch (InterruptedException e) { - // // silent - // } - // return returnCode; - // } - - private synchronized void notifyClose() { - if (returnCode == null) - returnCode = CANCEL; - notifyAll(); - } - - protected void closeShell(int returnCode) { - this.returnCode = returnCode; - if (CANCEL == returnCode) - onCancel(); - if (foregoundShell != null && !foregoundShell.isDisposed()) { - foregoundShell.close(); - foregoundShell.dispose(); - foregoundShell = null; - } - - if (backgroundShell != null && !backgroundShell.isDisposed()) { - backgroundShell.close(); - backgroundShell.dispose(); - } - } - - protected Point getInitialSize() { - // if (exception != null) - // return new Point(800, 600); - // else - return new Point(600, 400); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = new Composite(parent, SWT.NONE); - dialogarea.setLayout(new GridLayout()); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return dialogarea; - } - - protected Shell getBackgroundShell() { - return backgroundShell; - } - - protected Shell getForegoundShell() { - return foregoundShell; - } - - public void setBlockOnOpen(boolean shouldBlock) { - block = shouldBlock; - } - - public void pack() { - foregoundShell.pack(); - } - - private void runEventLoop(Shell loopShell) { - Display display; - if (foregoundShell == null) { - display = Display.getCurrent(); - } else { - display = loopShell.getDisplay(); - } - - while (loopShell != null && !loopShell.isDisposed()) { - try { - if (!display.readAndDispatch()) { - display.sleep(); - } - } catch (UnsupportedOperationException e) { - throw e; - } catch (Throwable e) { - handleException(e); - } - } - if (!display.isDisposed()) - display.update(); - } - - protected void handleException(Throwable t) { - if (t instanceof ThreadDeath) { - // Don't catch ThreadDeath as this is a normal occurrence when - // the thread dies - throw (ThreadDeath) t; - } - // Try to keep running. - t.printStackTrace(); - } - - /** @return false, if the dialog should not be closed. */ - protected boolean onCancel() { - return true; - } - - public void setTitle(String title) { - this.title = title; - if (title != null && getForegoundShell() != null) - getForegoundShell().setText(title); - } - - public Integer getReturnCode() { - return returnCode; - } - -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java deleted file mode 100644 index 9404b81da..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** A dialog asking a for a single value. */ -public class SingleValueDialog extends CmsMessageDialog { - private Text valueT; - private String value; - private String defaultValue; - - public SingleValueDialog(Shell parentShell, String message) { - super(parentShell, message, QUESTION); - } - - public SingleValueDialog(Shell parentShell, String message, String defaultValue) { - super(parentShell, message, QUESTION); - this.defaultValue = defaultValue; - } - - @Override - protected Control createInputArea(Composite parent) { - valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); - valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - if (defaultValue != null) - valueT.setText(defaultValue); - return valueT; - } - - @Override - protected void okPressed() { - value = valueT.getText(); - super.okPressed(); - } - - public String getString() { - return value; - } - - public Long getLong() { - return Long.valueOf(getString()); - } - - public Double getDouble() { - return Double.valueOf(getString()); - } - - public static String ask(String message) { - return ask(message, null); - } - - public static String ask(String message, String defaultValue) { - SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue); - if (svd.open() == Window.OK) - return svd.getString(); - else - return null; - } - - public static Long askLong(String message) { - SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); - if (svd.open() == Window.OK) - return svd.getLong(); - else - return null; - } - - public static Double askDouble(String message) { - SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); - if (svd.open() == Window.OK) - return svd.getDouble(); - else - return null; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java deleted file mode 100644 index ac76dba81..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace dialogs. */ -package org.argeo.cms.swt.dialogs; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java deleted file mode 100644 index 4039f2baa..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.argeo.cms.swt.gcr; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import javax.xml.namespace.QName; - -import org.argeo.api.acr.Content; -import org.argeo.cms.acr.fs.FsContentProvider; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TableItem; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; - -public class GcrContentTreeView extends Composite { - private Tree tree; - private Table table; - private Content rootContent; - - private Content selected; - - public GcrContentTreeView(Composite parent, int style, Content content) { - super(parent, style); - this.rootContent = content; - this.selected = rootContent; - setLayout(new GridLayout(2, false)); - initTree(); - GridData treeGd = CmsSwtUtils.fillHeight(); - treeGd.widthHint = 300; - tree.setLayoutData(treeGd); - initTable(); - - table.setLayoutData(CmsSwtUtils.fillAll()); - } - - protected void initTree() { - tree = new Tree(this, 0); - for (Content c : rootContent) { - TreeItem root = new TreeItem(tree, 0); - root.setText(c.getName().toString()); - root.setData(c); - new TreeItem(root, 0); - } - tree.addListener(SWT.Expand, event -> { - final TreeItem root = (TreeItem) event.item; - TreeItem[] items = root.getItems(); - for (TreeItem item : items) { - if (item.getData() != null) - return; - item.dispose(); - } - Content content = (Content) root.getData(); - for (Content c : content) { - TreeItem item = new TreeItem(root, 0); - item.setText(c.getName().toString()); - item.setData(c); - boolean hasChildren = true; - if (hasChildren) { - new TreeItem(item, 0); - } - } - }); - tree.addListener(SWT.Selection, event -> { - TreeItem item = (TreeItem) event.item; - selected = (Content) item.getData(); - refreshTable(); - }); - } - - protected void initTable() { - table = new Table(this, 0); - table.setLinesVisible(true); - table.setHeaderVisible(true); - TableColumn keyCol = new TableColumn(table, SWT.NONE); - keyCol.setText("Attribute"); - keyCol.setWidth(200); - TableColumn valueCol = new TableColumn(table, SWT.NONE); - valueCol.setText("Value"); - keyCol.setWidth(300); - refreshTable(); - } - - protected void refreshTable() { - for (TableItem item : table.getItems()) { - item.dispose(); - } - for (QName key : selected.keySet()) { - TableItem item = new TableItem(table, 0); - item.setText(0, key.toString()); - Object value = selected.get(key); - item.setText(1, value.toString()); - } - table.getColumn(0).pack(); - table.getColumn(1).pack(); - } - - public static void main(String[] args) { - Path basePath; - if (args.length > 0) { - basePath = Paths.get(args[0]); - } else { - basePath = Paths.get(System.getProperty("user.home")); - } - - final Display display = new Display(); - final Shell shell = new Shell(display); - shell.setText(basePath.toString()); - shell.setLayout(new FillLayout()); - - FsContentProvider contentSession = new FsContentProvider(basePath); -// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/")); - - shell.setSize(shell.computeSize(800, 600)); - shell.open(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - display.dispose(); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java deleted file mode 100644 index 8109c40ac..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.cms.swt.gcr; - -import org.argeo.api.acr.Content; -import org.argeo.api.cms.MvcProvider; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -@FunctionalInterface -public interface SwtUiProvider extends MvcProvider { - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java deleted file mode 100644 index b9b2751a7..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.argeo.cms.swt.osgi; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -import org.argeo.cms.osgi.BundleCmsTheme; -import org.argeo.cms.swt.CmsSwtTheme; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.widgets.Display; - -/** Centralises some generic {@link CmsSwtTheme} patterns. */ -public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme { - private Map imageCache = new HashMap<>(); - - private Map> 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 null if not found. - * - * @param name An icon file name without path and extension. - * @param preferredSize the preferred size, if null, - * {@link #getDefaultIconSize()} will be tried. - */ - public Image getIcon(String name, Integer preferredSize) { - if (preferredSize == null) - preferredSize = getDefaultIconSize(); - Map subCache; - if (!iconPaths.containsKey(name)) - subCache = new HashMap<>(); - else - subCache = iconPaths.get(name); - Image image = null; - if (!subCache.containsKey(preferredSize)) { - Image bestMatchSoFar = null; - paths: for (String p : getImagesPaths()) { - int lastSlash = p.lastIndexOf('/'); - String fileName = p; - if (lastSlash >= 0) - fileName = p.substring(lastSlash + 1); - int lastDot = fileName.lastIndexOf('.'); - if (lastDot >= 0) - fileName = fileName.substring(0, lastDot); - if (fileName.equals(name)) {// matched - Image img = getImage(p); - int width = img.getBounds().width; - if (width == preferredSize) {// perfect match - subCache.put(preferredSize, p); - image = img; - break paths; - } - if (bestMatchSoFar == null) { - bestMatchSoFar = img; - } else { - if (Math.abs(width - preferredSize) < Math - .abs(bestMatchSoFar.getBounds().width - preferredSize)) - bestMatchSoFar = img; - } - } - } - - if (image == null) - image = bestMatchSoFar; - } else { - image = getImage(subCache.get(preferredSize)); - } - - if (image != null && !iconPaths.containsKey(name)) - iconPaths.put(name, subCache); - - return image; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java deleted file mode 100644 index ed1bfd868..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java +++ /dev/null @@ -1,246 +0,0 @@ -package org.argeo.cms.swt.useradmin; - -import java.util.ArrayList; -import java.util.List; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Shell; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** Dialog with a user (or group) list to pick up one */ -public class PickUpUserDialog extends TrayDialog { - private static final long serialVersionUID = -1420106871173920369L; - - // Business objects - private final UserAdmin userAdmin; - private User selectedUser; - - // this page widgets and UI objects - private String title; - private LdifUsersTable userTableViewerCmp; - private TableViewer userViewer; - private List columnDefs = new ArrayList(); - - /** - * 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 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 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 users = new ArrayList(); - for (Role role : roles) - if (!users.contains(role)) - users.add((User) role); - return users; - } - } - - private boolean notNull(String string) { - if (string == null) - return false; - else - return !"".equals(string.trim()); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java deleted file mode 100644 index d1c90a43f..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.argeo.cms.swt.useradmin; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.UserAdminUtils; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** Centralize label providers for the group table */ -class UserLP extends ColumnLabelProvider { - private static final long serialVersionUID = -4645930210988368571L; - - final static String COL_ICON = "colID.icon"; - final static String COL_DN = "colID.dn"; - final static String COL_DISPLAY_NAME = "colID.displayName"; - final static String COL_DOMAIN = "colID.domain"; - - final String currType; - - // private Font italic; - private Font bold; - - UserLP(String colId) { - this.currType = colId; - } - - @Override - public Font getFont(Object element) { - // Current user as bold - if (UserAdminUtils.isCurrentUser(((User) element))) { - if (bold == null) - bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) - .createFont(Display.getCurrent()); - return bold; - } - return null; - } - - @Override - public Image getImage(Object element) { - if (COL_ICON.equals(currType)) { - User user = (User) element; - String dn = user.getName(); - if (dn.endsWith(CmsConstants.ROLES_BASEDN)) - return UsersImages.ICON_ROLE; - else if (user.getType() == Role.GROUP) - return UsersImages.ICON_GROUP; - else - return UsersImages.ICON_USER; - } else - return null; - } - - @Override - public String getText(Object element) { - User user = (User) element; - return getText(user); - - } - - public String getText(User user) { - if (COL_DN.equals(currType)) - return user.getName(); - else if (COL_DISPLAY_NAME.equals(currType)) - return UserAdminUtils.getCommonName(user); - else if (COL_DOMAIN.equals(currType)) - return UserAdminUtils.getDomainName(user); - else - return ""; - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java deleted file mode 100644 index 21fc5afba..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.cms.swt.useradmin; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.swt.graphics.Image; - -/** Specific users icons. */ -public class UsersImages { - private final static String PREFIX = "icons/"; - - public final static Image ICON_USER = CmsImages.createImg(PREFIX + "person.png"); - public final static Image ICON_GROUP = CmsImages.createImg(PREFIX + "group.png"); - public final static Image ICON_ROLE = CmsImages.createImg(PREFIX + "role.gif"); - public final static Image ICON_CHANGE_PASSWORD = CmsImages.createImg(PREFIX + "security.gif"); -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java deleted file mode 100644 index 3597bfc57..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace users management components. */ -package org.argeo.cms.swt.useradmin; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java deleted file mode 100644 index 1c4d79eee..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.argeo.cms.ui.theme; - -import java.net.URL; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -public class CmsImages { - private static BundleContext themeBc = FrameworkUtil.getBundle(CmsImages.class).getBundleContext(); - - final public static String ICONS_BASE = "icons/"; - final public static String TYPES_BASE = ICONS_BASE + "types/"; - final public static String ACTIONS_BASE = ICONS_BASE + "actions/"; - - public static Image createIcon(String name) { - return createImg(CmsImages.ICONS_BASE + name); - } - - public static Image createAction(String name) { - return createImg(CmsImages.ACTIONS_BASE + name); - } - - public static Image createType(String name) { - return createImg(CmsImages.TYPES_BASE + name); - } - - public static Image createImg(String name) { - return CmsImages.createDesc(name).createImage(Display.getDefault()); - } - - public static ImageDescriptor createDesc(String name) { - return createDesc(themeBc, name); - } - - public static ImageDescriptor createDesc(BundleContext bc, String name) { - URL url = bc.getBundle().getResource(name); - if (url == null) - return ImageDescriptor.getMissingImageDescriptor(); - return ImageDescriptor.createFromURL(url); - } - - public static Image createImg(BundleContext bc, String name) { - return createDesc(bc, name).createImage(Display.getDefault()); - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java deleted file mode 100644 index 7d3a260f3..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS core theme images. */ -package org.argeo.cms.ui.theme; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java deleted file mode 100644 index c882eb766..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Tree content provider dealing with tree objects and providing reasonable - * defaults. - */ -public abstract class AbstractTreeContentProvider implements - ITreeContentProvider { - private static final long serialVersionUID = 8246126401957763868L; - - /** Does nothing */ - public void dispose() { - } - - /** Does nothing */ - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - public Object[] getChildren(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).getChildren(); - } - return new Object[0]; - } - - public Object getParent(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).getParent(); - } - return null; - } - - public boolean hasChildren(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).hasChildren(); - } - return false; - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java deleted file mode 100644 index a38552c07..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** - * Wraps the definition of a column to be used in the various JFace viewers - * (typically tree and table). It enables definition of generic viewers which - * column can be then defined externally. Also used to generate export. - */ -public class ColumnDefinition { - private ColumnLabelProvider labelProvider; - private String label; - private int weight = 0; - private int minWidth = 120; - - public ColumnDefinition(ColumnLabelProvider labelProvider, String label) { - this.labelProvider = labelProvider; - this.label = label; - } - - public ColumnDefinition(ColumnLabelProvider labelProvider, String label, - int weight) { - this.labelProvider = labelProvider; - this.label = label; - this.weight = weight; - this.minWidth = weight; - } - - public ColumnDefinition(ColumnLabelProvider labelProvider, String label, - int weight, int minimumWidth) { - this.labelProvider = labelProvider; - this.label = label; - this.weight = weight; - this.minWidth = minimumWidth; - } - - public ColumnLabelProvider getLabelProvider() { - return labelProvider; - } - - public void setLabelProvider(ColumnLabelProvider labelProvider) { - this.labelProvider = labelProvider; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public int getWeight() { - return weight; - } - - public void setWeight(int weight) { - this.weight = weight; - } - - public int getMinWidth() { - return minWidth; - } - - public void setMinWidth(int minWidth) { - this.minWidth = minWidth; - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java deleted file mode 100644 index 9430a2083..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.ColumnViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerComparator; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; - -/** Generic column viewer sorter */ -public class ColumnViewerComparator extends ViewerComparator { - private static final long serialVersionUID = -2266218906355859909L; - - public static final int ASC = 1; - - public static final int NONE = 0; - - public static final int DESC = -1; - - private int direction = 0; - - private TableViewerColumn column; - - private ColumnViewer viewer; - - public ColumnViewerComparator(TableViewerColumn column) { - super(null); - this.column = column; - this.viewer = column.getViewer(); - this.column.getColumn().addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 7586796298965472189L; - - public void widgetSelected(SelectionEvent e) { - if (ColumnViewerComparator.this.viewer.getComparator() != null) { - if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) { - int tdirection = ColumnViewerComparator.this.direction; - - if (tdirection == ASC) { - setSortDirection(DESC); - } else if (tdirection == DESC) { - setSortDirection(NONE); - } - } else { - setSortDirection(ASC); - } - } else { - setSortDirection(ASC); - } - } - }); - } - - private void setSortDirection(int direction) { - if (direction == NONE) { - column.getColumn().getParent().setSortColumn(null); - column.getColumn().getParent().setSortDirection(SWT.NONE); - viewer.setComparator(null); - } else { - column.getColumn().getParent().setSortColumn(column.getColumn()); - this.direction = direction; - - if (direction == ASC) { - column.getColumn().getParent().setSortDirection(SWT.DOWN); - } else { - column.getColumn().getParent().setSortDirection(SWT.UP); - } - - if (viewer.getComparator() == this) { - viewer.refresh(); - } else { - viewer.setComparator(this); - } - - } - } - - public int compare(Viewer viewer, Object e1, Object e2) { - return direction * super.compare(viewer, e1, e2); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java deleted file mode 100644 index 37a36e859..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.eclipse.ui; - -/** CMS specific exceptions. */ -public class EclipseUiException extends RuntimeException { - private static final long serialVersionUID = -5341764743356771313L; - - public EclipseUiException(String message) { - super(message); - } - - public EclipseUiException(String message, Throwable e) { - super(message, e); - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java deleted file mode 100644 index 95b45fed6..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java +++ /dev/null @@ -1,195 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Utilities to simplify UI development. */ -public class EclipseUiUtils { - - /** Dispose all children of a Composite */ - public static void clear(Composite composite) { - for (Control child : composite.getChildren()) - child.dispose(); - } - - /** - * Enables efficient call to the layout method of a composite, refreshing only - * some of the children controls. - */ - public static void layout(Composite parent, Control... toUpdateControls) { - parent.layout(toUpdateControls); - } - - // - // FONTS - // - /** Shortcut to retrieve default italic font from display */ - public static Font getItalicFont(Composite parent) { - return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC) - .createFont(parent.getDisplay()); - } - - /** Shortcut to retrieve default bold font from display */ - public static Font getBoldFont(Composite parent) { - return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) - .createFont(parent.getDisplay()); - } - - /** Shortcut to retrieve default bold italic font from display */ - public static Font getBoldItalicFont(Composite parent) { - return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC) - .createFont(parent.getDisplay()); - } - - // - // Simplify grid layouts management - // - public static GridLayout noSpaceGridLayout() { - return noSpaceGridLayout(new GridLayout()); - } - - public static GridLayout noSpaceGridLayout(int columns) { - return noSpaceGridLayout(new GridLayout(columns, false)); - } - - public static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - public static GridData fillWidth() { - return grabWidth(SWT.FILL, SWT.FILL); - } - - public static GridData fillWidth(int colSpan) { - GridData gd = grabWidth(SWT.FILL, SWT.FILL); - gd.horizontalSpan = colSpan; - return gd; - } - - public static GridData fillAll() { - return new GridData(SWT.FILL, SWT.FILL, true, true); - } - - public static GridData fillAll(int colSpan, int rowSpan) { - return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan); - } - - public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { - return new GridData(horizontalAlignment, horizontalAlignment, true, false); - } - - // - // Simplify Form layout management - // - - /** - * Creates a basic form data that is attached to the 4 corners of the parent - * composite - */ - public static FormData fillFormData() { - FormData formData = new FormData(); - formData.top = new FormAttachment(0, 0); - formData.left = new FormAttachment(0, 0); - formData.right = new FormAttachment(100, 0); - formData.bottom = new FormAttachment(100, 0); - return formData; - } - - /** - * Create a label and a text field for a grid layout, the text field grabbing - * excess horizontal - * - * @param parent - * the parent composite - * @param label - * the label to display - * @param modifyListener - * a {@link ModifyListener} to listen on events on the text, can be - * null - * @return the created text - * - */ - // FIXME why was this deprecated. - // * @ deprecated use { @ link #createGridLT(Composite, String)} instead - // @ Deprecated - public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Text txt = new Text(parent, SWT.LEAD | SWT.BORDER); - txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - if (modifyListener != null) - txt.addModifyListener(modifyListener); - return txt; - } - - /** - * Create a label and a text field for a grid layout, the text field grabbing - * excess horizontal - */ - public static Text createGridLT(Composite parent, String label) { - return createGridLT(parent, label, null); - } - - /** - * Creates one label and a text field not editable with background colour of the - * parent (like a label but with selectable text) - */ - public static Text createGridLL(Composite parent, String label, String text) { - Text txt = createGridLT(parent, label); - txt.setText(text); - txt.setEditable(false); - txt.setBackground(parent.getBackground()); - return txt; - } - - /** - * Create a label and a text field with password display for a grid layout, the - * text field grabbing excess horizontal - */ - public static Text createGridLP(Composite parent, String label) { - return createGridLP(parent, label, null); - } - - /** - * Create a label and a text field with password display for a grid layout, the - * text field grabbing excess horizontal. The given modify listener will be - * added to the newly created text field if not null. - */ - public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD); - txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - if (modifyListener != null) - txt.addModifyListener(modifyListener); - return txt; - } - - // MISCELLANEOUS - - /** Simply checks if a string is not null nor empty */ - public static boolean notEmpty(String stringToTest) { - return !(stringToTest == null || "".equals(stringToTest.trim())); - } - - /** Simply checks if a string is null or empty */ - public static boolean isEmpty(String stringToTest) { - return stringToTest == null || "".equals(stringToTest.trim()); - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java deleted file mode 100644 index e82505df5..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.argeo.eclipse.ui; - -import java.io.InputStream; - -/** - * Used for file download : subclasses must implement model specific methods to - * get a byte array representing a file given is ID. - */ -@Deprecated -public interface FileProvider { - - public byte[] getByteArrayFileFromId(String fileId); - - public InputStream getInputStreamFromFileId(String fileId); - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java deleted file mode 100644 index e1d8b05ea..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerComparator; - -public abstract class GenericTableComparator extends ViewerComparator { - private static final long serialVersionUID = -1175894935075325810L; - protected int propertyIndex; - public static final int ASCENDING = 0, DESCENDING = 1; - protected int direction = DESCENDING; - - /** - * Creates an instance of a sorter for TableViewer. - * - * @param defaultColumnIndex - * the default sorter column - */ - - public GenericTableComparator(int defaultColumnIndex, int direction) { - propertyIndex = defaultColumnIndex; - this.direction = direction; - } - - public void setColumn(int column) { - if (column == this.propertyIndex) { - // Same column as last sort; toggle the direction - direction = 1 - direction; - } else { - // New column; do a descending sort - this.propertyIndex = column; - direction = DESCENDING; - } - } - - /** - * Must be Overriden in each view. - */ - public abstract int compare(Viewer viewer, Object e1, Object e2); -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java deleted file mode 100644 index ac7b2d8fb..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java +++ /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 getColumnDefinition(String extractId); -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java deleted file mode 100644 index cf3c15795..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java +++ /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 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(); - } - - 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, super.dispose() 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 List getChildrenOfType(Class clss) { - List lst = new ArrayList(); - for (Object obj : children) { - if (clss.isAssignableFrom(obj.getClass())) - lst.add((T) obj); - } - return lst; - } - - public synchronized boolean hasChildren() { - return children.size() > 0; - } - - public Object getChildByName(String name) { - for (Object child : children) { - if (child.toString().equals(name)) - return child; - } - return null; - } - - public synchronized Boolean isLoaded() { - return loaded; - } - - public String getName() { - return name; - } - - public void setParent(TreeParent parent) { - this.parent = parent; - if (parent != null && parent.path != null) - this.path = parent.path + '/' + name; - else - this.path = '/' + name; - } - - public TreeParent getParent() { - return parent; - } - - public String toString() { - return getName(); - } - - public int compareTo(TreeParent o) { - return name.compareTo(o.name); - } - - @Override - public int hashCode() { - if (path != null) - return path.hashCode(); - else - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (path != null && obj instanceof TreeParent) - return path.equals(((TreeParent) obj).path); - else - return name.equals(obj.toString()); - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java deleted file mode 100644 index a388e745e..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.argeo.api.cms.CmsLog; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Generic error dialog to be used in try/catch blocks. - * - * @deprecated Use CMS dialogs instead. - */ -@Deprecated -public class ErrorFeedback extends TitleAreaDialog { - private static final long serialVersionUID = -8918084784628179044L; - - private final static CmsLog log = CmsLog.getLog(ErrorFeedback.class); - - private final String message; - private final Throwable exception; - - public static void show(String message, Throwable e) { - // rethrow ThreaDeath in order to make sure that RAP will properly clean - // up the UI thread - if (e instanceof ThreadDeath) - throw (ThreadDeath) e; - - new ErrorFeedback(newShell(), message, e).open(); - } - - public static void show(String message) { - new ErrorFeedback(newShell(), message, null).open(); - } - - private static Shell newShell() { - return new Shell(getDisplay(), SWT.NO_TRIM); - } - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public ErrorFeedback(Shell parentShell, String message, Throwable e) { - super(parentShell); - setShellStyle(SWT.NO_TRIM); - this.message = message; - this.exception = e; - log.error(message, e); - } - - protected Point getInitialSize() { - if (exception != null) - return new Point(800, 600); - else - return new Point(400, 300); - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - setMessage(message != null ? message + (exception != null ? ": " + exception.getMessage() : "") - : exception != null ? exception.getMessage() : "Unkown Error", IMessageProvider.ERROR); - - if (exception != null) { - Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - stack.setEditable(false); - stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - stack.setText(sw.toString()); - } - - parent.pack(); - return composite; - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText("Error"); - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java deleted file mode 100644 index f2715bc05..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Generic lightweight dialog, not based on JFace. - * - * @deprecated Use CMS dialogs instead. - */ -@Deprecated -public class FeedbackDialog extends LightweightDialog { - private final static CmsLog log = CmsLog.getLog(FeedbackDialog.class); - - private String message; - private Throwable exception; - -// private Shell parentShell; - private Shell shell; - - public static void show(String message, Throwable e) { - // rethrow ThreaDeath in order to make sure that RAP will properly clean - // up the UI thread - if (e instanceof ThreadDeath) - throw (ThreadDeath) e; - - new FeedbackDialog(getDisplay().getActiveShell(), message, e).open(); - } - - public static void show(String message) { - new FeedbackDialog(getDisplay().getActiveShell(), message, null).open(); - } - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public FeedbackDialog(Shell parentShell, String message, Throwable e) { - super(parentShell); - this.message = message; - this.exception = e; - log.error(message, e); - } - - public int open() { - if (shell != null) - throw new EclipseUiException("There is already a shell"); - shell = new Shell(getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - shell.setLayout(new GridLayout()); - // shell.setText("Error"); - shell.setSize(getInitialSize()); - createDialogArea(shell); - // shell.pack(); - // shell.layout(); - - Rectangle shellBounds = Display.getCurrent().getBounds();// RAP - Point dialogSize = shell.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - shell.setLocation(x, y); - - shell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = -2701270481953688763L; - - @Override - public void shellDeactivated(ShellEvent e) { - closeShell(); - } - }); - - shell.open(); - return OK; - } - - protected void closeShell() { - shell.close(); - shell.dispose(); - shell = null; - } - - protected Point getInitialSize() { - // if (exception != null) - // return new Point(800, 600); - // else - return new Point(400, 300); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = new Composite(parent, SWT.NONE); - dialogarea.setLayout(new GridLayout()); - // Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Label messageLbl = new Label(dialogarea, SWT.NONE); - if (message != null) - messageLbl.setText(message); - else if (exception != null) - messageLbl.setText(exception.getLocalizedMessage()); - - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - if (exception != null) { - Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - stack.setEditable(false); - stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - stack.setText(sw.toString()); - } - - // parent.pack(); - return composite; - } -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java deleted file mode 100644 index 615e1417a..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** Generic lightweight dialog, not based on JFace. */ -@Deprecated -public class LightweightDialog { - private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); - - // must be the same value as org.eclipse.jface.window.Window#OK - public final static int OK = 0; - // must be the same value as org.eclipse.jface.window.Window#CANCEL - public final static int CANCEL = 1; - - private Shell parentShell; - private Shell backgroundShell; - private Shell foregoundShell; - - private Integer returnCode = null; - private boolean block = true; - - private String title; - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public LightweightDialog(Shell parentShell) { - this.parentShell = parentShell; - } - - public int open() { - if (foregoundShell != null) - throw new EclipseUiException("There is already a shell"); - backgroundShell = new Shell(parentShell, SWT.ON_TOP); - backgroundShell.setFullScreen(true); - // if (parentShell != null) { - // backgroundShell.setBounds(parentShell.getBounds()); - // } else - // backgroundShell.setMaximized(true); - backgroundShell.setAlpha(128); - backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); - foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); - if (title != null) - setTitle(title); - foregoundShell.setLayout(new GridLayout()); - foregoundShell.setSize(getInitialSize()); - createDialogArea(foregoundShell); - // shell.pack(); - // shell.layout(); - - Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP - Point dialogSize = foregoundShell.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - foregoundShell.setLocation(x, y); - - foregoundShell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = -2701270481953688763L; - - @Override - public void shellDeactivated(ShellEvent e) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - - @Override - public void shellClosed(ShellEvent e) { - notifyClose(); - } - - }); - - backgroundShell.open(); - foregoundShell.open(); - // after the foreground shell has been opened - backgroundShell.addFocusListener(new FocusListener() { - private static final long serialVersionUID = 3137408447474661070L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - }); - - if (block) { - block(); - } - if (returnCode == null) - returnCode = OK; - return returnCode; - } - - public void block() { - try { - runEventLoop(foregoundShell); - } catch (ThreadDeath t) { - returnCode = CANCEL; - if (log.isTraceEnabled()) - log.error("Thread death, canceling dialog", t); - } catch (Throwable t) { - returnCode = CANCEL; - log.error("Cannot open blocking lightweight dialog", t); - } - } - - private boolean hasChildShells() { - if (foregoundShell == null) - return false; - return foregoundShell.getShells().length != 0; - } - - // public synchronized int openAndWait() { - // open(); - // while (returnCode == null) - // try { - // wait(100); - // } catch (InterruptedException e) { - // // silent - // } - // return returnCode; - // } - - private synchronized void notifyClose() { - if (returnCode == null) - returnCode = CANCEL; - notifyAll(); - } - - protected void closeShell(int returnCode) { - this.returnCode = returnCode; - if (CANCEL == returnCode) - onCancel(); - if (foregoundShell != null && !foregoundShell.isDisposed()) { - foregoundShell.close(); - foregoundShell.dispose(); - foregoundShell = null; - } - - if (backgroundShell != null && !backgroundShell.isDisposed()) { - backgroundShell.close(); - backgroundShell.dispose(); - } - } - - protected Point getInitialSize() { - // if (exception != null) - // return new Point(800, 600); - // else - return new Point(600, 400); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = new Composite(parent, SWT.NONE); - dialogarea.setLayout(new GridLayout()); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return dialogarea; - } - - protected Shell getBackgroundShell() { - return backgroundShell; - } - - protected Shell getForegoundShell() { - return foregoundShell; - } - - public void setBlockOnOpen(boolean shouldBlock) { - block = shouldBlock; - } - - public void pack() { - foregoundShell.pack(); - } - - private void runEventLoop(Shell loopShell) { - Display display; - if (foregoundShell == null) { - display = Display.getCurrent(); - } else { - display = loopShell.getDisplay(); - } - - while (loopShell != null && !loopShell.isDisposed()) { - try { - if (!display.readAndDispatch()) { - display.sleep(); - } - } catch (UnsupportedOperationException e) { - throw e; - } catch (Throwable e) { - handleException(e); - } - } - if (!display.isDisposed()) - display.update(); - } - - protected void handleException(Throwable t) { - if (t instanceof ThreadDeath) { - // Don't catch ThreadDeath as this is a normal occurrence when - // the thread dies - throw (ThreadDeath) t; - } - // Try to keep running. - t.printStackTrace(); - } - - /** @return false, if the dialog should not be closed. */ - protected boolean onCancel() { - return true; - } - - public void setTitle(String title) { - this.title = title; - if (title != null && getForegoundShell() != null) - getForegoundShell().setText(title); - } - - public Integer getReturnCode() { - return returnCode; - } - -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java deleted file mode 100644 index 8ce9b44fb..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Dialog to retrieve a single value. - * - * @deprecated Use CMS dialogs instead. - */ -@Deprecated -public class SingleValue extends TitleAreaDialog { - private static final long serialVersionUID = 2843538207460082349L; - - private Text valueT; - private String value; - private final String title, message, label; - private final Boolean multiline; - - public static String ask(String label, String message) { - SingleValue svd = new SingleValue(label, message); - if (svd.open() == Window.OK) - return svd.getString(); - else - return null; - } - - public static Long askLong(String label, String message) { - SingleValue svd = new SingleValue(label, message); - if (svd.open() == Window.OK) - return svd.getLong(); - else - return null; - } - - public static Double askDouble(String label, String message) { - SingleValue svd = new SingleValue(label, message); - if (svd.open() == Window.OK) - return svd.getDouble(); - else - return null; - } - - public SingleValue(String label, String message) { - this(Display.getDefault().getActiveShell(), label, message, label, false); - } - - public SingleValue(Shell parentShell, String title, String message, String label, Boolean multiline) { - super(parentShell); - this.title = title; - this.message = message; - this.label = label; - this.multiline = multiline; - } - - protected Point getInitialSize() { - if (multiline) - return new Point(450, 350); - - else - return new Point(400, 270); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayoutData(EclipseUiUtils.fillAll()); - GridLayout layout = new GridLayout(2, false); - layout.marginWidth = layout.marginHeight = 20; - composite.setLayout(layout); - - valueT = createLT(composite, label); - - setMessage(message, IMessageProvider.NONE); - - parent.pack(); - valueT.setFocus(); - return composite; - } - - @Override - protected void okPressed() { - value = valueT.getText(); - super.okPressed(); - } - - /** Creates label and text. */ - protected Text createLT(Composite parent, String label) { - new Label(parent, SWT.NONE).setText(label); - Text text; - if (multiline) { - text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.MULTI); - text.setLayoutData(EclipseUiUtils.fillAll()); - } else { - text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); - text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - } - return text; - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText(title); - } - - public String getString() { - return value; - } - - public Long getLong() { - return Long.valueOf(getString()); - } - - public Double getDouble() { - return Double.valueOf(getString()); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java deleted file mode 100644 index d6ab1481e..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace dialogs. */ -package org.argeo.eclipse.ui.dialogs; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java deleted file mode 100644 index c01b2d751..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java +++ /dev/null @@ -1,450 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedHashMap; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Text; - -/** Simple UI provider that populates a composite parent given a NIO path */ -public class AdvancedFsBrowser { - private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class); - - // Some local constants to experiment. should be cleaned - // private final static int THUMBNAIL_WIDTH = 400; - // private Point imageWidth = new Point(250, 0); - private final static int COLUMN_WIDTH = 160; - - private Path initialPath; - private Path currEdited; - // Filter - private Composite displayBoxCmp; - private Text parentPathTxt; - private Text filterTxt; - // Browser columns - private ScrolledComposite scrolledCmp; - // Keep a cache of the opened directories - private LinkedHashMap 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 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 = "Unknown"; - addProperty(parent, "Type", mimeType); - addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false)); - } - parent.layout(true, true); - } catch (IOException e) { - throw new FsUiException("Cannot display details for " + context, e); - } - } - - private void addProperty(Composite parent, String propName, String value) { - Label contextL = new Label(parent, SWT.NONE); - contextL.setText(propName + ": " + value); - } - - /** - * Almost canonical implementation of a table that displays the content of a - * directory - */ - private class FilterEntitiesVirtualTable extends Composite { - private static final long serialVersionUID = 2223410043691844875L; - - // Context - private Path context; - private Path currSelected = null; - - // UI Objects - private FsTableViewer viewer; - - @Override - public boolean setFocus() { - if (viewer.getTable().isDisposed()) - return false; - if (currSelected != null) - viewer.setSelection(new StructuredSelection(currSelected), true); - else if (viewer.getSelection().isEmpty()) { - Object first = viewer.getElementAt(0); - if (first != null) - viewer.setSelection(new StructuredSelection(first), true); - } - return viewer.getTable().setFocus(); - } - - /** - * Enable highlighting the correct element in the table when externally browsing - * (typically via the command-line-like Text field) - */ - void setSelected(Path selected) { - // to prevent change selection event to be thrown - currSelected = selected; - viewer.setSelection(new StructuredSelection(currSelected), true); - } - - void filterList(String filter) { - viewer.setInput(context, filter); - } - - public FilterEntitiesVirtualTable(Composite parent, int style, Path context) { - super(parent, SWT.NO_FOCUS); - this.context = context; - createTableViewer(this); - } - - private void createTableViewer(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - // We must limit the size of the table otherwise the full list is - // loaded before the layout happens - // Composite listCmp = new Composite(parent, SWT.NO_FOCUS); - // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); - // gd.widthHint = COLUMN_WIDTH; - // listCmp.setLayoutData(gd); - // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); - // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI | - // SWT.V_SCROLL); - // Table table = viewer.getTable(); - // table.setLayoutData(EclipseUiUtils.fillAll()); - - viewer = new FsTableViewer(parent, SWT.MULTI); - Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH); - - viewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); - if (selection.isEmpty()) - return; - Object obj = selection.getFirstElement(); - Path newSelected; - if (obj instanceof Path) - newSelected = (Path) obj; - else if (obj instanceof ParentDir) - newSelected = ((ParentDir) obj).getPath(); - else - return; - if (newSelected.equals(currSelected)) - return; - currSelected = newSelected; - setEdited(newSelected); - - } - }); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8083424284436715709L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (e.keyCode == SWT.ARROW_RIGHT) { - if (!Files.isDirectory(selected)) - return; - if (selected != null) { - setEdited(selected); - browserCols.get(selected).setFocus(); - } - } else if (e.keyCode == SWT.ARROW_LEFT) { - if (context.equals(initialPath)) - return; - Path parent = context.getParent(); - if (parent == null) - return; - - setEdited(parent); - browserCols.get(parent).setFocus(); - } - } - }); - } - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java deleted file mode 100644 index d3fc1c903..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Files; -import java.nio.file.Path; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** Basic label provider with icon for NIO file viewers */ -public class FileIconNameLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = 8187902187946523148L; - - private Image folderIcon; - private Image fileIcon; - - public FileIconNameLabelProvider() { - // if (!PlatformUI.isWorkbenchRunning()) { - folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage(); - fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage(); - // } - } - - @Override - public void dispose() { - if (folderIcon != null) - folderIcon.dispose(); - if (fileIcon != null) - fileIcon.dispose(); - super.dispose(); - } - - @Override - public String getText(Object element) { - if (element instanceof Path) { - Path curr = ((Path) element); - Path name = curr.getFileName(); - if (name == null) - return "[No name]"; - else - return name.toString(); - } else if (element instanceof ParentDir) { - return ".."; - } - return null; - } - - @Override - public Image getImage(Object element) { - if (element instanceof Path) { - Path curr = ((Path) element); - if (Files.isDirectory(curr)) - // if (folderIcon != null) - return folderIcon; - // else - // return - // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); - // else if (fileIcon != null) - return fileIcon; - // else - // return - // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); - } else if (element instanceof ParentDir) { - return folderIcon; - } - return null; - } - - @Override - public String getToolTipText(Object element) { - if (element instanceof Path) { - Path curr = ((Path) element); - Path name = curr.getFileName(); - if (name == null) - return "[No name]"; - else - return name.toAbsolutePath().toString(); - } else if (element instanceof ParentDir) { - return ((ParentDir) element).getPath().toAbsolutePath().toString(); - } - return null; - } - -} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java deleted file mode 100644 index 3b126e90b..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java +++ /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 columns) { - this.setContentProvider(new MyLazyCP()); - Table table = this.getTable(); - table.setLinesVisible(true); - table.setHeaderVisible(true); - // CmsUtils.markup(table); - // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); - for (ColumnDefinition colDef : columns) { - TableViewerColumn column = new TableViewerColumn(this, SWT.NONE); - column.setLabelProvider(colDef.getLabelProvider()); - TableColumn tcol = column.getColumn(); - tcol.setResizable(true); - tcol.setText(colDef.getLabel()); - tcol.setWidth(colDef.getMinWidth()); - } - return table; - } - - public void setInput(Path dir, String filter) { - Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder); - if (rows == null) { - this.setInput(null); - this.setItemCount(0); - return; - } - boolean isRoot; - try { - isRoot = dir.getRoot().equals(dir); - } catch (Exception e) { - // FIXME Workaround for JCR root node access - isRoot = dir.toString().equals("/"); - } - final Object[] res; - if (isRoot) - res = rows; - else if (initialPath != null && initialPath.equals(dir)) - res = rows; - else { - res = new Object[rows.length + 1]; - res[0] = new ParentDir(dir.getParent()); - for (int i = 1; i < res.length; i++) { - res[i] = rows[i - 1]; - } - } - this.setInput(res); - int length = res.length; - this.setItemCount(length); - this.refresh(); - } - - /** Directly displays bookmarks **/ - public void setPathsInput(Path... paths) { - this.setInput((Object[]) paths); - this.setItemCount(paths.length); - this.refresh(); - } - - /** - * A path which is to be considered as root (and thus provide no link to a - * parent directory) - */ - public void setInitialPath(Path initialPath) { - this.initialPath = initialPath; - } - - private class MyLazyCP implements ILazyContentProvider { - private static final long serialVersionUID = 9096550041395433128L; - private Object[] elements; - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // IMPORTANT: don't forget this: an exception will be thrown if - // a selected object is not part of the results anymore. - viewer.setSelection(null); - this.elements = (Object[]) newInput; - } - - public void updateElement(int index) { - if (index < elements.length) - FsTableViewer.this.replace(elements[index], index); - } - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java deleted file mode 100644 index f55ead718..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java +++ /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 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 stream = Files.newDirectoryStream(dir, filter)) { - // TODO make this lazy - List 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 children = Files.newDirectoryStream(path, "*")) { - return children.iterator().hasNext(); - } - } catch (IOException e) { - throw new FsUiException("Unable to check child existence on " + path, e); - } - } - - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java deleted file mode 100644 index 2b51e71a2..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -/** Centralize constants used by the Nio FS UI parts */ -public interface FsUiConstants { - - // TODO use standard properties - String PROPERTY_NAME = "name"; - String PROPERTY_SIZE = "size"; - String PROPERTY_LAST_MODIFIED = "last-modified"; - String PROPERTY_TYPE = "type"; -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java deleted file mode 100644 index 422b0e1ad..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -/** Files specific exception */ -public class FsUiException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public FsUiException(String message) { - super(message); - } - - public FsUiException(String message, Throwable e) { - super(message, e); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java deleted file mode 100644 index 956d96bb5..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java +++ /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 pairs = new ArrayList<>(); - try (DirectoryStream 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 { - Path p; - Boolean i; - }; - - static class LPair extends Pair { - long v; - - public LPair(Path path, long propValue) { - p = path; - v = propValue; - } - - public LPair(Path path, long propValue, boolean isDir) { - p = path; - v = propValue; - i = isDir; - } - - public int compareTo(Object o) { - if (i != null) { - Boolean j = ((LPair) o).i; - if (i.booleanValue() != j.booleanValue()) - return i.booleanValue() ? -1 : 1; - } - long u = ((LPair) o).v; - return v < u ? -1 : v == u ? 0 : 1; - } - }; - - static class SPair extends Pair { - String v; - - public SPair(Path path, String propValue) { - p = path; - v = propValue; - } - - public SPair(Path path, String propValue, boolean isDir) { - p = path; - v = propValue; - i = isDir; - } - - public int compareTo(Object o) { - if (i != null) { - Boolean j = ((SPair) o).i; - if (i.booleanValue() != j.booleanValue()) - return i.booleanValue() ? -1 : 1; - } - String u = ((SPair) o).v; - return v.compareTo(u); - } - }; -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java deleted file mode 100644 index 2bb65eed0..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** Expect a {@link Path} as input element */ -public class NioFileLabelProvider extends ColumnLabelProvider { - private final static FileTime EPOCH = FileTime.fromMillis(0); - private static final long serialVersionUID = 2160026425187796930L; - private final String propName; - private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm"); - - // TODO use new formatting - // DateTimeFormatter formatter = - // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT ) - // .withLocale( Locale.UK ) - // .withZone( ZoneId.systemDefault() ); - public NioFileLabelProvider(String propName) { - this.propName = propName; - } - - @Override - public String getText(Object element) { - try { - Path path; - if (element instanceof ParentDir) { -// switch (propName) { -// case FsUiConstants.PROPERTY_SIZE: -// return "-"; -// case FsUiConstants.PROPERTY_LAST_MODIFIED: -// return "-"; -// // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString(); -// case FsUiConstants.PROPERTY_TYPE: -// return "Folder"; -// } - path = ((ParentDir) element).getPath(); - } else - path = (Path) element; - switch (propName) { - case FsUiConstants.PROPERTY_SIZE: - if (Files.isDirectory(path)) - return "-"; - else - return FsUiUtils.humanReadableByteCount(Files.size(path), false); - case FsUiConstants.PROPERTY_LAST_MODIFIED: - if (Files.isDirectory(path)) - return "-"; - FileTime time = Files.getLastModifiedTime(path); - if (time.equals(EPOCH)) - return "-"; - else - return dateFormat.format(new Date(time.toMillis())); - case FsUiConstants.PROPERTY_TYPE: - if (Files.isDirectory(path)) - return "Folder"; - else { - String mimeType = Files.probeContentType(path); - if (EclipseUiUtils.isEmpty(mimeType)) - return "Unknown"; - else - return mimeType; - } - default: - throw new IllegalArgumentException("Unsupported property " + propName); - } - } catch (IOException ioe) { - throw new FsUiException("Cannot get property " + propName + " on " + element); - } - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java deleted file mode 100644 index 6f09c2905..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Path; - -/** A parent directory (..) reference. */ -public class ParentDir { - Path path; - - public ParentDir(Path path) { - super(); - this.path = path; - } - - public Path getPath() { - return path; - } - - @Override - public int hashCode() { - return path.hashCode(); - } - - @Override - public String toString() { - return "Parent dir " + path; - } - -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java deleted file mode 100644 index 2e3d6b405..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Table; - -/** - * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on - * the left hand side and a simple table on the right hand side. - */ -public class SimpleFsBrowser extends Composite { - private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class); - private static final long serialVersionUID = -40347919096946585L; - - private Path currSelected; - private FsTableViewer bookmarksViewer; - private FsTableViewer directoryDisplayViewer; - - public SimpleFsBrowser(Composite parent, int style) { - super(parent, style); - createContent(this); - // parent.layout(true, true); - } - - public Viewer getViewer() { - return directoryDisplayViewer; - } - - private void createContent(Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - SashForm form = new SashForm(parent, SWT.HORIZONTAL); - Composite leftCmp = new Composite(form, SWT.NONE); - populateBookmarks(leftCmp); - - Composite rightCmp = new Composite(form, SWT.BORDER); - populateDisplay(rightCmp); - form.setLayoutData(EclipseUiUtils.fillAll()); - form.setWeights(new int[] { 1, 3 }); - } - - public void setInput(Path... paths) { - bookmarksViewer.setPathsInput(paths); - bookmarksViewer.getTable().getParent().layout(true, true); - } - - private void populateBookmarks(final Composite parent) { - // GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); - // layout.verticalSpacing = 5; - parent.setLayout(new GridLayout()); - - ISelectionChangedListener selList = new MySelectionChangedListener(); - - appendTitle(parent, "My bookmarks"); - bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL); - Table table = bookmarksViewer.configureDefaultSingleColumnTable(500); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - bookmarksViewer.addSelectionChangedListener(selList); - - appendTitle(parent, "Jcr + File"); - - FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL); - table = jcrFilesViewers.configureDefaultSingleColumnTable(500); - gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - jcrFilesViewers.addSelectionChangedListener(selList); - - // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider(); - // try { - // Path testPath = fsProvider.getPath(new URI("jcr+memory:/")); - // jcrFilesViewers.setPathsInput(testPath); - // } catch (URISyntaxException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - } - - private Label appendTitle(Composite parent, String value) { - Label titleLbl = new Label(parent, SWT.NONE); - titleLbl.setText(value); - titleLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 5; - gd.verticalIndent = 5; - titleLbl.setLayoutData(gd); - return titleLbl; - } - - private class MySelectionChangedListener implements ISelectionChangedListener { - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection(); - if (selection.isEmpty()) - return; - else { - Path newSelected = (Path) selection.getFirstElement(); - if (newSelected.equals(currSelected)) - return; - currSelected = newSelected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } - } - - private void populateDisplay(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); - List colDefs = new ArrayList<>(); - colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), - "Last modified", 200)); - Table table = directoryDisplayViewer.configureDefaultTable(colDefs); - table.setLayoutData(EclipseUiUtils.fillAll()); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8083424284436715709L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - log.debug("Key event received: " + e.keyCode); - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (e.keyCode == SWT.CR) { - if (!Files.isDirectory(selected)) - return; - if (selected != null) { - currSelected = selected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } else if (e.keyCode == SWT.BS) { - currSelected = currSelected.getParent(); - directoryDisplayViewer.setInput(currSelected, "*"); - directoryDisplayViewer.getTable().setFocus(); - } - } - }); - -// directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { -// @Override -// public void doubleClick(DoubleClickEvent event) { -// IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); -// Path selected = null; -// if (!selection.isEmpty()) { -// Object obj = selection.getFirstElement(); -// if (obj instanceof Path) -// selected = (Path) obj; -// else if (obj instanceof ParentDir) -// selected = ((ParentDir) obj).getPath(); -// } -// if (selected != null) { -// if (!Files.isDirectory(selected)) -// return; -// currSelected = selected; -// directoryDisplayViewer.setInput(currSelected, "*"); -// } -// } -// }); - - directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { - @Override - public void doubleClick(DoubleClickEvent event) { - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) { - Object obj = selection.getFirstElement(); - if (obj instanceof Path) - selected = (Path) obj; - else if (obj instanceof ParentDir) - selected = ((ParentDir) obj).getPath(); - } - if (selected != null) { - if (!Files.isDirectory(selected)) - return; - currSelected = selected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } - }); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java deleted file mode 100644 index 401e5cb5e..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Tree; - -/** A simple Java 7 nio files browser with a tree */ -public class SimpleFsTreeBrowser extends Composite { - private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class); - private static final long serialVersionUID = -40347919096946585L; - - private Path currSelected; - private FsTreeViewer treeViewer; - private FsTableViewer directoryDisplayViewer; - - public SimpleFsTreeBrowser(Composite parent, int style) { - super(parent, style); - createContent(this); - // parent.layout(true, true); - } - - private void createContent(Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - SashForm form = new SashForm(parent, SWT.HORIZONTAL); - Composite child1 = new Composite(form, SWT.NONE); - populateTree(child1); - Composite child2 = new Composite(form, SWT.BORDER); - populateDisplay(child2); - form.setLayoutData(EclipseUiUtils.fillAll()); - form.setWeights(new int[] { 1, 3 }); - } - - public void setInput(Path... paths) { - treeViewer.setPathsInput(paths); - treeViewer.getControl().getParent().layout(true, true); - } - - private void populateTree(final Composite parent) { - // GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); - // layout.verticalSpacing = 5; - parent.setLayout(new GridLayout()); - - ISelectionChangedListener selList = new MySelectionChangedListener(); - - treeViewer = new FsTreeViewer(parent, SWT.MULTI); - Tree tree = treeViewer.configureDefaultSingleColumnTable(500); - GridData gd = EclipseUiUtils.fillAll(); - // gd.horizontalIndent = 10; - tree.setLayoutData(gd); - treeViewer.addSelectionChangedListener(selList); - } - - private class MySelectionChangedListener implements ISelectionChangedListener { - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection(); - if (selection.isEmpty()) - return; - else { - Path newSelected = (Path) selection.getFirstElement(); - if (newSelected.equals(currSelected)) - return; - currSelected = newSelected; - if (Files.isDirectory(currSelected)) - directoryDisplayViewer.setInput(currSelected, "*"); - } - } - } - - private void populateDisplay(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); - List colDefs = new ArrayList<>(); - colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), - "Last modified", 100, 100)); - Table table = directoryDisplayViewer.configureDefaultTable(colDefs); - table.setLayoutData(EclipseUiUtils.fillAll()); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8083424284436715709L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - log.debug("Key event received: " + e.keyCode); - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (e.keyCode == SWT.CR) { - if (!Files.isDirectory(selected)) - return; - if (selected != null) { - currSelected = selected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } else if (e.keyCode == SWT.BS) { - currSelected = currSelected.getParent(); - directoryDisplayViewer.setInput(currSelected, "*"); - directoryDisplayViewer.getTable().setFocus(); - } - } - }); - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png deleted file mode 100644 index ce2f2a8f4..000000000 Binary files a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png deleted file mode 100644 index c31f37e07..000000000 Binary files a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png and /dev/null differ diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java deleted file mode 100644 index d7f83c3e1..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace file system utilities. */ -package org.argeo.eclipse.ui.fs; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java deleted file mode 100644 index 0d245db07..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace utilities. */ -package org.argeo.eclipse.ui; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java deleted file mode 100644 index 57139056c..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java +++ /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 TableViewer. 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 columnDefs = new ArrayList(); - 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 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 getSelectedUsers() { - if (hasSelectionColumn) { - Object[] elements = ((CheckboxTableViewer) usersViewer).getCheckedElements(); - - List result = new ArrayList(); - 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 listFilteredElements(String filter); - - // protected List listFilteredElements(String filter) { - // List users = new ArrayList(); - // 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 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(" Less... "); - gd.heightHint = SWT.DEFAULT; - } else { - moreLk.setText(" More... "); - gd.heightHint = 0; - } - forceLayout(); - } - } - - private void forceLayout() { - LdifUsersTable.this.getParent().layout(true, true); - } - - private TableViewer createTableViewer(final Composite parent) { - - int style = tableStyle | SWT.H_SCROLL | SWT.V_SCROLL; - if (hasSelectionColumn) - style = style | SWT.CHECK; - Table table = new Table(parent, style); - TableColumnLayout layout = new TableColumnLayout(); - - // TODO the table layout does not works with the scrolled form - - if (preventTableLayout) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - table.setLayoutData(EclipseUiUtils.fillAll()); - } else - parent.setLayout(layout); - - TableViewer viewer; - if (hasSelectionColumn) - viewer = new CheckboxTableViewer(table); - else - viewer = new TableViewer(table); - table.setLinesVisible(true); - table.setHeaderVisible(true); - - TableViewerColumn column; - // int offset = 0; - if (hasSelectionColumn) { - // offset = 1; - column = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 25); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = 1L; - - @Override - public String getText(Object element) { - return null; - } - }); - layout.setColumnData(column.getColumn(), new ColumnWeightData(25, 25, false)); - - SelectionAdapter selectionAdapter = new SelectionAdapter() { - private static final long serialVersionUID = 1L; - - boolean allSelected = false; - - @Override - public void widgetSelected(SelectionEvent e) { - allSelected = !allSelected; - ((CheckboxTableViewer) usersViewer).setAllChecked(allSelected); - } - }; - column.getColumn().addSelectionListener(selectionAdapter); - } - - // NodeViewerComparator comparator = new NodeViewerComparator(); - // TODO enable the sort by click on the header - // int i = offset; - for (ColumnDefinition colDef : columnDefs) - createTableColumn(viewer, layout, colDef); - - // column = ViewerUtils.createTableViewerColumn(viewer, - // colDef.getHeaderLabel(), SWT.NONE, colDef.getColumnSize()); - // column.setLabelProvider(new CLProvider(colDef.getPropertyName())); - // column.getColumn().addSelectionListener( - // JcrUiUtils.getNodeSelectionAdapter(i, - // colDef.getPropertyType(), colDef.getPropertyName(), - // comparator, viewer)); - // i++; - // } - - // IMPORTANT: initialize comparator before setting it - // JcrColumnDefinition firstCol = colDefs.get(0); - // comparator.setColumn(firstCol.getPropertyType(), - // firstCol.getPropertyName()); - // viewer.setComparator(comparator); - - return viewer; - } - - /** Default creation of a column for a user table */ - private TableViewerColumn createTableColumn(TableViewer tableViewer, TableColumnLayout layout, - ColumnDefinition columnDef) { - - boolean resizable = true; - TableViewerColumn tvc = new TableViewerColumn(tableViewer, SWT.NONE); - TableColumn column = tvc.getColumn(); - - column.setText(columnDef.getLabel()); - column.setWidth(columnDef.getMinWidth()); - column.setResizable(resizable); - - ColumnLabelProvider lp = columnDef.getLabelProvider(); - // add a reference to the display to enable font management - // if (lp instanceof UserAdminAbstractLP) - // ((UserAdminAbstractLP) lp).setDisplay(tableViewer.getTable() - // .getDisplay()); - tvc.setLabelProvider(lp); - - layout.setColumnData(column, new ColumnWeightData(columnDef.getWeight(), columnDef.getMinWidth(), resizable)); - - return tvc; - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java deleted file mode 100644 index 9e93b1106..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace composites. */ -package org.argeo.eclipse.ui.parts; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java deleted file mode 100644 index 8f4df1799..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.argeo.eclipse.ui.util; - -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.TreeViewerColumn; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TreeColumn; - -/** - * Centralise useful methods to manage JFace Table, Tree and TreeColumn viewers. - */ -public class ViewerUtils { - - /** - * Creates a basic column for the given table. For the time being, we do not - * support movable columns. - */ - public static TableColumn createColumn(Table parent, String name, int style, int width) { - TableColumn result = new TableColumn(parent, style); - result.setText(name); - result.setWidth(width); - result.setResizable(true); - return result; - } - - /** - * Creates a TableViewerColumn for the given viewer. For the time being, we do - * not support movable columns. - */ - public static TableViewerColumn createTableViewerColumn(TableViewer parent, String name, int style, int width) { - TableViewerColumn tvc = new TableViewerColumn(parent, style); - TableColumn column = tvc.getColumn(); - column.setText(name); - column.setWidth(width); - column.setResizable(true); - return tvc; - } - - // public static TableViewerColumn createTableViewerColumn(TableViewer parent, - // Localized name, int style, int width) { - // return createTableViewerColumn(parent, name.lead(), style, width); - // } - - /** - * Creates a TreeViewerColumn for the given viewer. For the time being, we do - * not support movable columns. - */ - public static TreeViewerColumn createTreeViewerColumn(TreeViewer parent, String name, int style, int width) { - TreeViewerColumn tvc = new TreeViewerColumn(parent, style); - TreeColumn column = tvc.getColumn(); - column.setText(name); - column.setWidth(width); - column.setResizable(true); - return tvc; - } -} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java deleted file mode 100644 index 798d17482..000000000 --- a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR helpers. */ -package org.argeo.eclipse.ui.util; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/.classpath b/jcr/org.argeo.cms.jcr/.classpath deleted file mode 100644 index 4a00becd8..000000000 --- a/jcr/org.argeo.cms.jcr/.classpath +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/.project b/jcr/org.argeo.cms.jcr/.project deleted file mode 100644 index 3e470f829..000000000 --- a/jcr/org.argeo.cms.jcr/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.jcr - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs b/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 7e2e11935..000000000 --- a/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,101 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled -org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullable.secondary= -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.problem.APILeak=warning -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.autoboxing=ignore -org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning -org.eclipse.jdt.core.compiler.problem.deadCode=warning -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore -org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore -org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore -org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore -org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled -org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning -org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning -org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning -org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore -org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore -org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore -org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning -org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore -org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore -org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning -org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled -org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore -org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml deleted file mode 100644 index f5fc8deaa..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml deleted file mode 100644 index a283ef075..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml deleted file mode 100644 index 5fb56e3ed..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml deleted file mode 100644 index a94b15168..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml deleted file mode 100644 index e26453b06..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml deleted file mode 100644 index b43b51920..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml deleted file mode 100644 index a0885bbc5..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml deleted file mode 100644 index db2bfaa26..000000000 --- a/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/bnd.bnd b/jcr/org.argeo.cms.jcr/bnd.bnd deleted file mode 100644 index 065f0d15d..000000000 --- a/jcr/org.argeo.cms.jcr/bnd.bnd +++ /dev/null @@ -1,40 +0,0 @@ -Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator - -Provide-Capability:\ -cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\ -cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\ -cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\ -osgi.service;objectClass="javax.jcr.Repository" - -Import-Package:\ -org.argeo.cms.servlet,\ -javax.jcr.security,\ -org.h2;resolution:=optional;version="[1,3)",\ -org.postgresql;version="[42,43)";resolution:=optional,\ -org.apache.commons.httpclient.cookie;resolution:=optional,\ -org.osgi.framework.namespace;version=0.0.0,\ -org.osgi.*;version=0.0.0,\ -org.osgi.service.http.whiteboard,\ -org.apache.jackrabbit.api.stats;version="[1,4)",\ -org.apache.jackrabbit.api;version="[1,4)",\ -org.apache.jackrabbit.commons;version="[1,4)",\ -org.apache.jackrabbit.spi;version="[1,4)",\ -org.apache.jackrabbit.spi2dav;version="[1,4)",\ -org.apache.jackrabbit.spi2davex;version="[1,4)",\ -org.apache.jackrabbit.webdav.jcr;version="[1,4)",\ -org.apache.jackrabbit.webdav.server;version="[1,4)",\ -org.apache.jackrabbit.webdav.simple;version="[1,4)",\ -org.apache.jackrabbit.*;version="[1,4)",\ -junit.*;resolution:=optional,\ -javax.servlet.*;version="[3,5)",\ -* - -Service-Component:\ -OSGI-INF/repositoryContextsFactory.xml,\ -OSGI-INF/jcrRepositoryFactory.xml,\ -OSGI-INF/jcrFsProvider.xml,\ -OSGI-INF/jcrDeployment.xml,\ -OSGI-INF/jcrServletContext.xml,\ -OSGI-INF/dataServletContext.xml,\ -OSGI-INF/filesServletContext.xml,\ -OSGI-INF/filesServlet.xml diff --git a/jcr/org.argeo.cms.jcr/build.properties b/jcr/org.argeo.cms.jcr/build.properties deleted file mode 100644 index 2b99dd7ba..000000000 --- a/jcr/org.argeo.cms.jcr/build.properties +++ /dev/null @@ -1,10 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - OSGI-INF/ -source.. = src/ -additional.bundles = \ -org.apache.jackrabbit.data, \ -org.apache.jackrabbit.spi.commons,\ - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java deleted file mode 100644 index 40d38eec2..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.argeo.cms.fs; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.nio.file.spi.FileSystemProvider; - -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.query.Query; -import javax.jcr.query.QueryManager; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.jcr.Jcr; - -/** Utilities around documents. */ -public class CmsFsUtils { - // TODO make it more robust and configurable - private static String baseWorkspaceName = CmsConstants.SYS_WORKSPACE; - - public static Node getNode(Repository repository, Path path) { - String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString(); - String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString(); - try { - Session newSession; - try { - newSession = repository.login(workspaceName); - } catch (NoSuchWorkspaceException e) { - // base workspace - newSession = repository.login(baseWorkspaceName); - jcrPath = path.toString(); - } - return newSession.getNode(jcrPath); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get node from path " + path, e); - } - } - - public static NodeIterator getLastUpdatedDocuments(Session session) { - try { - String qStr = "//element(*, nt:file)"; - qStr += " order by @jcr:lastModified descending"; - QueryManager queryManager = session.getWorkspace().getQueryManager(); - @SuppressWarnings("deprecation") - Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH); - xpathQuery.setLimit(8); - NodeIterator nit = xpathQuery.execute().getNodes(); - return nit; - } catch (RepositoryException e) { - throw new IllegalStateException("Unable to retrieve last updated documents", e); - } - } - - public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) { - try { - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) - fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); - String path = uri.getPath(); - return fileSystem.getPath(path); - } catch (IOException e) { - throw new IllegalStateException("Unable to initialise file system for " + uri, e); - } - } - - public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) { - String workspaceName = Jcr.getWorkspaceName(node); - String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node) - : '/' + workspaceName + Jcr.getPath(node); - URI uri; - try { - uri = new URI(CmsConstants.SCHEME_NODE, null, fullPath, null); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e); - } - return getPath(nodeFileSystemProvider, uri); - } - - /** Singleton. */ - private CmsFsUtils() { - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java deleted file mode 100644 index c2898577e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.util.Properties; - -import org.apache.jackrabbit.core.config.BeanConfig; -import org.apache.jackrabbit.core.config.ConfigurationException; -import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; -import org.apache.jackrabbit.core.util.db.ConnectionFactory; -import org.w3c.dom.Element; - -/** - * A {@link RepositoryConfigurationParser} providing more flexibility with - * classloaders. - */ -@SuppressWarnings("restriction") -class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser { - private ClassLoader classLoader = null; - - public CustomRepositoryConfigurationParser(Properties variables) { - super(variables); - } - - public CustomRepositoryConfigurationParser(Properties variables, ConnectionFactory connectionFactory) { - super(variables, connectionFactory); - } - - @Override - protected RepositoryConfigurationParser createSubParser(Properties variables) { - Properties props = new Properties(getVariables()); - props.putAll(variables); - CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props, - connectionFactory); - subParser.setClassLoader(classLoader); - return subParser; - } - - @Override - public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException { - WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent); - workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader); - return workspaceSecurityConfig; - } - - @Override - protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException { - BeanConfig beanConfig = super.parseBeanConfig(parent, name); - if (beanConfig.getClassName().startsWith("org.argeo")) { - beanConfig.setClassLoader(classLoader); - } - return beanConfig; - } - - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java deleted file mode 100644 index 40c83f6df..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.internal.jcr; - -/** Pre-defined Jackrabbit repository configurations. */ -enum JackrabbitType { - /** Local file system */ - localfs, - /** Embedded Java H2 database */ - h2, - /** Embedded Java H2 database in PostgreSQL compatibility mode */ - h2_postgresql, - /** PostgreSQL */ - postgresql, - /** PostgreSQL with datastore */ - postgresql_ds, - /** PostgreSQL with cluster */ - postgresql_cluster, - /** PostgreSQL with cluster and datastore */ - postgresql_cluster_ds, - /** Memory */ - memory; -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java deleted file mode 100644 index 0536fb645..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsConstants; -import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; -import org.argeo.jcr.JcrException; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; - -/** JCR specific init utilities. */ -public class JcrInitUtils { - private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class); - private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext(); - - public static void addToDeployment(CmsDeployment nodeDeployment) { - // node repository -// Dictionary provided = null; - Dictionary provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, - CmsConstants.NODE); - Dictionary 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 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 getNodeRepositoryConfig(Dictionary provided) { - Dictionary props = provided != null ? provided : new Hashtable(); - 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 getRepositoryConfig(String dataModelName, - Dictionary provided) { - if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY)) - throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); - Dictionary props = provided != null ? provided : new Hashtable(); - 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 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 params = new HashMap(); - params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString()); - // TODO make it configurable - params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE); - return repositoryFactory.getRepository(params); - } - - private static String getFrameworkProp(String key, String def) { - String value; - if (bundleContext != null) - value = bundleContext.getProperty(key); - else - value = System.getProperty(key); - if (value == null) - return def; - return value; - } - - private static String getFrameworkProp(String key) { - return getFrameworkProp(key, null); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java deleted file mode 100644 index dba005cb4..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java +++ /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; - -/** - * experimental Duplicate added entries in another directory (typically a - * remote mount). - */ -@SuppressWarnings("restriction") -public class LocalFsDataStore extends FileDataStore { - String redundantPath; - FileDataStore redundantStore; - - @Override - public void init(String homeDir) { - // init primary first - super.init(homeDir); - - if (redundantPath != null) { - // redundant directory must be created first - // TODO implement some polling? - if (Files.exists(Paths.get(redundantPath))) { - redundantStore = new FileDataStore(); - redundantStore.setPath(redundantPath); - redundantStore.init(homeDir); - } - } - } - - @Override - public DataRecord addRecord(InputStream input) throws DataStoreException { - DataRecord dataRecord = super.addRecord(input); - syncRedundantRecord(dataRecord); - return dataRecord; - } - - @Override - public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException { - DataRecord dataRecord = super.getRecord(identifier); - syncRedundantRecord(dataRecord); - return dataRecord; - } - - protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException { - if (redundantStore == null) - return; - if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) { - try (InputStream redundant = dataRecord.getStream()) { - redundantStore.addRecord(redundant); - } catch (IOException e) { - throw new DataStoreException("Cannot add redundant record.", e); - } - } - } - - public void setRedundantPath(String redundantPath) { - this.redundantPath = redundantPath; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java deleted file mode 100644 index a45656cf5..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.osgi.metatype.EnumAD; -import org.argeo.osgi.metatype.EnumOCD; - -/** JCR repository configuration */ -public enum RepoConf implements EnumAD { - /** Repository type */ - type("h2"), - /** Default workspace */ - defaultWorkspace(CmsConstants.SYS_WORKSPACE), - /** Database URL */ - dburl(null), - /** Database user */ - dbuser(null), - /** Database password */ - dbpassword(null), - - /** The identifier (can be an URL locating the repo) */ - labeledUri(null), - // - // JACKRABBIT SPECIFIC - // - /** Maximum database pool size */ - maxPoolSize(10), - /** Maximum cache size in MB */ - maxCacheMB(null), - /** Bundle cache size in MB */ - bundleCacheMB(8), - /** Extractor pool size */ - extractorPoolSize(0), - /** Search cache size */ - searchCacheSize(1000), - /** Max volatile index size */ - maxVolatileIndexSize(1048576), - /** Cluster id (if appropriate configuration) */ - clusterId("default"), - /** Indexes base path */ - indexesBase(null); - - /** The default value. */ - private Object def; - private String oid; - - RepoConf(String oid, Object def) { - this.oid = oid; - this.def = def; - } - - RepoConf(Object def) { - this.def = def; - } - - public Object getDefault() { - return def; - } - - @Override - public String getID() { - if (oid != null) - return oid; - return EnumAD.super.getID(); - } - - public static class OCD extends EnumOCD { - public OCD(String locale) { - super(RepoConf.class, locale); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java deleted file mode 100644 index 3db97167c..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java +++ /dev/null @@ -1,225 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.Properties; -import java.util.UUID; - -import javax.jcr.RepositoryException; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.cache.CacheManager; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.internal.CmsPaths; -import org.xml.sax.InputSource; - -/** Can interpret properties in order to create an actual JCR repository. */ -public class RepositoryBuilder { - private final static CmsLog log = CmsLog.getLog(RepositoryBuilder.class); - - public RepositoryContext createRepositoryContext(Dictionary 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 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 properties) { - Properties props = new Properties(); - for (Enumeration 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 properties, RepoConf key) { - Object value = properties.get(key.name()); - if (value == null) - return key.getDefault() != null ? key.getDefault().toString() : null; - else - return value.toString(); - } - - private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException { - ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(RepositoryBuilder.class.getClassLoader()); - try { - long begin = System.currentTimeMillis(); - // - // Actual repository creation - // - RepositoryContext repositoryContext = RepositoryContext.create(repositoryConfig); - - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - if (log.isDebugEnabled()) - log.debug( - "Created Jackrabbit repository in " + duration + " s, home: " + repositoryConfig.getHomeDir()); - - return repositoryContext; - } finally { - Thread.currentThread().setContextClassLoader(currentContextCl); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml deleted file mode 100644 index ace0fa5ee..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml deleted file mode 100644 index 430367656..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml deleted file mode 100644 index b88907919..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml deleted file mode 100644 index 3630a149d..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml deleted file mode 100644 index de2f245ad..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml deleted file mode 100644 index 488ad6b72..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml deleted file mode 100644 index b430674c9..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml deleted file mode 100644 index 5229d1660..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java deleted file mode 100644 index 4a28dca77..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java +++ /dev/null @@ -1,281 +0,0 @@ -package org.argeo.cms.jcr; - -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.security.auth.AuthPermission; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; - -/** Utilities related to Argeo model in JCR */ -public class CmsJcrUtils { - /** - * Wraps the call to the repository factory based on parameter - * {@link CmsConstants#CN} in order to simplify it and protect against future - * API changes. - */ - public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) { - try { - Map parameters = new HashMap(); - 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 parameters = new HashMap(); - 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() { - - @Override - public Session run() { - try { - return repository.login(workspaceName); - } catch (NoSuchWorkspaceException e) { - throw new IllegalArgumentException("No workspace " + workspaceName + " available", e); - } catch (RepositoryException e) { - throw new RuntimeException("Cannot open data admin session", e); - } - } - - }); - } finally { - Thread.currentThread().setContextClassLoader(currentCl); - } - } - - /** Singleton. */ - private CmsJcrUtils() { - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java deleted file mode 100644 index 04c5d2d8c..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.argeo.cms.jcr.acr; - -import java.util.Calendar; -import java.util.Iterator; -import java.util.Optional; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; -import javax.jcr.nodetype.NodeType; -import javax.xml.namespace.QName; - -import org.argeo.api.acr.Content; -import org.argeo.api.acr.spi.AbstractContent; -import org.argeo.api.acr.spi.ProvidedSession; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; - -public class JcrContent extends AbstractContent { - private Node jcrNode; - - private JcrContentProvider provider; - private ProvidedSession session; - - protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) { - this.session = session; - this.provider = provider; - this.jcrNode = node; - } - - @Override - public QName getName() { - return session.parsePrefixedName(Jcr.getName(jcrNode)); - } - - @Override - public Optional get(QName key, Class clss) { - if (isDefaultAttrTypeRequested(clss)) { - return Optional.of((A) get(jcrNode, key.toString())); - } - return Optional.of((A) Jcr.get(jcrNode, key.toString())); - } - - @Override - public Iterator iterator() { - try { - return new JcrContentIterator(jcrNode.getNodes()); - } catch (RepositoryException e) { - throw new JcrException("Cannot list children of " + jcrNode, e); - } - } - - @Override - protected Iterable keys() { - return new Iterable() { - - @Override - public Iterator 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 { - 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 { - private final JcrContentProvider contentSession; - private final PropertyIterator propertyIterator; - - protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) { - this.contentSession = contentSession; - this.propertyIterator = propertyIterator; - } - - @Override - public boolean hasNext() { - return propertyIterator.hasNext(); - } - - @Override - public QName next() { - Property property = null; - try { - property = propertyIterator.nextProperty(); - // TODO map standard property names - return session.parsePrefixedName(property.getName()); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve property " + property, null); - } - } - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java deleted file mode 100644 index ef8e375d0..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.cms.jcr.acr; - -import java.util.Arrays; -import java.util.Iterator; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.xml.namespace.NamespaceContext; - -import org.argeo.api.acr.Content; -import org.argeo.api.acr.spi.ContentProvider; -import org.argeo.api.acr.spi.ProvidedSession; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -public class JcrContentProvider implements ContentProvider, NamespaceContext { - private Repository jcrRepository; - private Session adminSession; - - public void init() { - adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null); - } - - public void destroy() { - JcrUtils.logoutQuietly(adminSession); - } - - public void setJcrRepository(Repository jcrRepository) { - this.jcrRepository = jcrRepository; - } - - @Override - public Content get(ProvidedSession session, String mountPath, String relativePath) { - // TODO Auto-generated method stub - return null; - } - - /* - * NAMESPACE CONTEXT - */ - @Override - public String getNamespaceURI(String prefix) { - try { - return adminSession.getNamespaceURI(prefix); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - @Override - public String getPrefix(String namespaceURI) { - try { - return adminSession.getNamespacePrefix(namespaceURI); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - @Override - public Iterator getPrefixes(String namespaceURI) { - try { - return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd deleted file mode 100644 index c9e6ee7e2..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd +++ /dev/null @@ -1,34 +0,0 @@ - - -// GENERIC TYPES -[argeo:remoteRepository] > nt:unstructured -- argeo:uri (STRING) -- argeo:userID (STRING) -+ argeo:password (argeo:encrypted) - -// TABULAR CONTENT -[argeo:table] > nt:file -+ * (argeo:column) * - -[argeo:column] > mix:title -- jcr:requiredType (STRING) = 'STRING' - -[argeo:csv] > nt:resource - -// CRYPTO -[argeo:encrypted] -mixin -// initialization vector used by some algorithms -- argeo:iv (BINARY) - -[argeo:pbeKeySpec] -mixin -- argeo:secretKeyFactory (STRING) -- argeo:salt (BINARY) -- argeo:iterationCount (LONG) -- argeo:keyLength (LONG) -- argeo:secretKeyEncryption (STRING) - -[argeo:pbeSpec] > argeo:pbeKeySpec -mixin -- argeo:cipher (STRING) diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd deleted file mode 100644 index 80849be95..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd +++ /dev/null @@ -1,10 +0,0 @@ -// DN (see https://tools.ietf.org/html/rfc4514) - - - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java deleted file mode 100644 index 16f5979d7..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java +++ /dev/null @@ -1,476 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; -import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX; - -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.callback.CallbackHandler; -import javax.servlet.Servlet; - -import org.apache.jackrabbit.commons.cnd.CndImporter; -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.internal.jcr.JcrInitUtils; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet; -import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet; -import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils; -import org.argeo.cms.osgi.DataModelNamespace; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.security.Keyring; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.LangUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWire; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.service.cm.ManagedService; -import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; -import org.osgi.util.tracker.ServiceTracker; - -/** Implementation of a CMS deployment. */ -public class CmsJcrDeployment { - private final CmsLog log = CmsLog.getLog(getClass()); - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - private DataModels dataModels; - private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG; - - private boolean argeoDataModelExtensionsAvailable = false; - - // Readiness - private boolean nodeAvailable = false; - - CmsDeployment cmsDeployment; - - public void start() { - dataModels = new DataModels(bc); - - ServiceTracker repoContextSt = new RepositoryContextStc(); - repoContextSt.open(); - //KernelUtils.asyncOpen(repoContextSt); - -// nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class); - - JcrInitUtils.addToDeployment(cmsDeployment); - - } - - public void stop() { -// if (nodeHttp != null) -// nodeHttp.destroy(); - - try { - for (ServiceReference 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 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> initRepositorySr; - try { - initRepositorySr = bc.getServiceReferences(Repository.class, - "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")"); - } catch (InvalidSyntaxException e1) { - throw new IllegalArgumentException(e1); - } - Iterator> it = initRepositorySr.iterator(); - while (it.hasNext()) { - ServiceReference 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 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(bc, CallbackHandler.class, null) { - - @Override - public CallbackHandler addingService(ServiceReference 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 publishAsLocalRepo) { - Session adminSession = KernelUtils.openAdminSession(repository); - try { - Set processed = new HashSet(); - 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 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 processed, - boolean importListedAbstractModels, List publishAsLocalRepo) { - // recursively process requirements first - List requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); - for (BundleWire wire : requiredWires) { - processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo); - } - - List 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 processed) { - Map 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 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 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 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 { - - public RepositoryContextStc() { - super(bc, RepositoryContext.class, null); - } - - @Override - public RepositoryContext addingService(ServiceReference reference) { - RepositoryContext repoContext = bc.getService(reference); - String cn = (String) reference.getProperty(CmsConstants.CN); - if (cn != null) { - List 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 reference, RepositoryContext service) { - } - - @Override - public void removedService(ServiceReference reference, RepositoryContext service) { - } - - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java deleted file mode 100644 index 0099b3bed..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystemAlreadyExistsException; -import java.nio.file.Path; -import java.nio.file.spi.FileSystemProvider; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider; -import org.argeo.jcr.fs.JcrFileSystem; -import org.argeo.jcr.fs.JcrFileSystemProvider; -import org.argeo.jcr.fs.JcrFsException; - -/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */ -public class CmsJcrFsProvider extends AbstractJackrabbitFsProvider { - private Map fileSystems = new HashMap<>(); - - private RepositoryFactory repositoryFactory; - private Repository repository; - - @Override - public String getScheme() { - return CmsConstants.SCHEME_NODE; - } - - @Override - public FileSystem newFileSystem(URI uri, Map 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()); - } catch (IOException e) { - throw new JcrFsException("Could not autocreate file system", e); - } - return fileSystem.getPath(path); - } - - protected JcrFileSystem currentUserFileSystem() { - String username = CurrentUser.getUsername(); - return fileSystems.get(username); - } - - public Node getUserHome(Repository repository) { - try { - Session session = repository.login(CmsConstants.HOME_WORKSPACE); - return CmsJcrUtils.getUserHome(session); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get user home", e); - } - } - - public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - this.repositoryFactory = repositoryFactory; - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - static class CmsFileSystem extends JcrFileSystem { - public CmsFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException { - super(provider, repository); - } - - public boolean skipNode(Node node) throws RepositoryException { -// if (node.isNodeType(NodeType.NT_HIERARCHY_NODE) || node.isNodeType(NodeTypes.NODE_USER_HOME) -// || node.isNodeType(NodeTypes.NODE_GROUP_HOME)) - if (node.isNodeType(NodeType.NT_HIERARCHY_NODE)) - return false; - // FIXME Better identifies home - if (node.hasProperty(Property.JCR_ID)) - return false; - return true; - } - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java deleted file mode 100644 index e7f5a55af..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.nio.file.Path; - -/** Centralises access to the default node deployment directories. */ -public class CmsPaths { - public static Path getRepoDirPath(String cn) { - return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_REPOS + '/' + cn); - } - - public static Path getRepoIndexesBase() { - return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_INDEXES); - } - - /** Singleton. */ - private CmsPaths() { - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java deleted file mode 100644 index 69b98dc3a..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.util.GregorianCalendar; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.nodetype.NodeType; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -import javax.jcr.observation.EventListener; -import javax.jcr.version.VersionManager; - -import org.apache.jackrabbit.api.JackrabbitValue; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrUtils; - -/** Ensure consistency of files, folder and last modified nodes. */ -class CmsWorkspaceIndexer implements EventListener { - private final static CmsLog log = CmsLog.getLog(CmsWorkspaceIndexer.class); - -// private final static String MIX_ETAG = "mix:etag"; - private final static String JCR_ETAG = "jcr:etag"; -// private final static String JCR_LAST_MODIFIED = "jcr:lastModified"; -// private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; -// private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; - private final static String JCR_DATA = "jcr:data"; - private final static String JCR_CONTENT = "jcr:data"; - - private String cn; - private String workspaceName; - private RepositoryImpl repositoryImpl; - private Session session; - private VersionManager versionManager; - - private LinkedBlockingDeque toProcess = new LinkedBlockingDeque<>(); - private IndexingThread indexingThread; - private AtomicBoolean stopping = new AtomicBoolean(false); - - public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName) - throws RepositoryException { - this.cn = cn; - this.workspaceName = workspaceName; - this.repositoryImpl = repositoryImpl; - } - - public void init() { - session = KernelUtils.openAdminSession(repositoryImpl, workspaceName); - try { - String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED }; - session.getWorkspace().getObservationManager().addEventListener(this, - Event.NODE_ADDED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true); - versionManager = session.getWorkspace().getVersionManager(); - - indexingThread = new IndexingThread(); - indexingThread.start(); - } catch (RepositoryException e1) { - throw new IllegalStateException(e1); - } - } - - public void destroy() { - stopping.set(true); - indexingThread.interrupt(); - // TODO make it configurable - try { - indexingThread.join(10 * 60 * 1000); - } catch (InterruptedException e1) { - log.warn("Indexing thread interrupted. Will log out session."); - } - - try { - session.getWorkspace().getObservationManager().removeEventListener(this); - } catch (RepositoryException e) { - if (log.isTraceEnabled()) - log.warn("Cannot unregistered JCR event listener", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - private synchronized void processEvents(EventIterator events) { - long begin = System.currentTimeMillis(); - long count = 0; - while (events.hasNext()) { - Event event = events.nextEvent(); - try { - toProcess.put(event); - } catch (InterruptedException e) { - e.printStackTrace(); - } -// processEvent(event); - count++; - } - long duration = System.currentTimeMillis() - begin; - if (log.isTraceEnabled()) - log.trace("Processed " + count + " events in " + duration + " ms"); - notifyAll(); - } - - protected synchronized void processEvent(Event event) { - try { - String eventPath = event.getPath(); - if (event.getType() == Event.NODE_ADDED) { - if (!versionManager.isCheckedOut(eventPath)) - return;// ignore checked-in nodes - if (log.isTraceEnabled()) - log.trace("NODE_ADDED " + eventPath); -// session.refresh(true); - session.refresh(false); - Node node = session.getNode(eventPath); - Node parentNode = node.getParent(); - if (parentNode.isNodeType(NodeType.NT_FILE)) { - if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) { - if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) - node.addMixin(NodeType.MIX_LAST_MODIFIED); - Property property = node.getProperty(Property.JCR_DATA); - String etag = toEtag(property.getValue()); - session.save(); - node.setProperty(JCR_ETAG, etag); - if (log.isTraceEnabled()) - log.trace("ETag and last modified added to new " + node); - } else if (node.isNodeType(NodeType.NT_RESOURCE)) { -// if (!node.isNodeType(MIX_ETAG)) -// node.addMixin(MIX_ETAG); -// session.save(); -// Property property = node.getProperty(Property.JCR_DATA); -// String etag = toEtag(property.getValue()); -// node.setProperty(JCR_ETAG, etag); -// session.save(); - } -// setLastModifiedRecursive(parentNode, event); -// session.save(); -// if (log.isTraceEnabled()) -// log.trace("ETag and last modified added to new " + node); - } - -// if (node.isNodeType(NodeType.NT_FOLDER)) { -// setLastModifiedRecursive(node, event); -// session.save(); -// if (log.isTraceEnabled()) -// log.trace("Last modified added to new " + node); -// } - } else if (event.getType() == Event.PROPERTY_CHANGED) { - String propertyName = extractItemName(eventPath); - // skip if last modified properties are explicitly set - if (!propertyName.equals(JCR_DATA)) - return; -// if (propertyName.equals(JCR_LAST_MODIFIED)) -// return; -// if (propertyName.equals(JCR_LAST_MODIFIED_BY)) -// return; -// if (propertyName.equals(JCR_MIXIN_TYPES)) -// return; -// if (propertyName.equals(JCR_ETAG)) -// return; - - if (log.isTraceEnabled()) - log.trace("PROPERTY_CHANGED " + eventPath); - - if (!session.propertyExists(eventPath)) - return; - session.refresh(false); - Property property = session.getProperty(eventPath); - Node node = property.getParent(); - if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA) - && node.isNodeType(NodeType.NT_UNSTRUCTURED)) { - String etag = toEtag(property.getValue()); - node.setProperty(JCR_ETAG, etag); - Node parentNode = node.getParent(); - if (parentNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - setLastModified(parentNode, event); - } - if (log.isTraceEnabled()) - log.trace("ETag and last modified updated for " + node); - } -// setLastModified(node, event); -// session.save(); -// if (log.isTraceEnabled()) -// log.trace("ETag and last modified updated for " + node); - } else if (event.getType() == Event.NODE_REMOVED) { - String removeNodePath = eventPath; - String nodeName = extractItemName(eventPath); - if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow - return; - if (log.isTraceEnabled()) - log.trace("NODE_REMOVED " + eventPath); -// String parentPath = JcrUtils.parentPath(removeNodePath); -// session.refresh(true); -// setLastModified(parentPath, event); -// session.save(); - if (log.isTraceEnabled()) - log.trace("Last modified updated for parents of removed " + removeNodePath); - } - } catch (Exception e) { - if (log.isTraceEnabled()) - log.warn("Cannot process event " + event, e); - } finally { -// try { -// session.refresh(true); -// if (session.hasPendingChanges()) -// session.save(); -//// session.refresh(false); -// } catch (RepositoryException e) { -// if (log.isTraceEnabled()) -// log.warn("Cannot refresh JCR session", e); -// } - } - - } - - private String extractItemName(String path) { - if (path == null || path.length() <= 1) - return null; - int lastIndex = path.lastIndexOf('/'); - if (lastIndex >= 0) { - return path.substring(lastIndex + 1); - } else { - return path; - } - } - - @Override - public void onEvent(EventIterator events) { - processEvents(events); -// Runnable toRun = new Runnable() { -// -// @Override -// public void run() { -// processEvents(events); -// } -// }; -// Future future = Activator.getInternalExecutorService().submit(toRun); -// try { -// // make the call synchronous -// future.get(60, TimeUnit.SECONDS); -// } catch (TimeoutException | ExecutionException | InterruptedException e) { -// // silent -// } - } - - static String toEtag(Value v) { - if (v instanceof JackrabbitValue) { - JackrabbitValue value = (JackrabbitValue) v; - return '\"' + value.getContentIdentity() + '\"'; - } else { - return null; - } - - } - - protected synchronized void setLastModified(Node node, Event event) throws RepositoryException { - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTimeInMillis(event.getDate()); - node.setProperty(Property.JCR_LAST_MODIFIED, calendar); - node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID()); - if (log.isTraceEnabled()) - log.trace("Last modified set on " + node); - } - - /** Recursively set the last updated time on parents. */ - protected synchronized void setLastModifiedRecursive(Node node, Event event) throws RepositoryException { - if (versionManager.isCheckedOut(node.getPath())) { - if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - setLastModified(node, event); - } - if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - node.addMixin(NodeType.MIX_LAST_MODIFIED); - if (log.isTraceEnabled()) - log.trace("Last modified mix-in added to " + node); - } - - } - - // end condition - if (node.getDepth() == 0) { -// try { -// node.getSession().save(); -// } catch (RepositoryException e) { -// log.warn("Cannot index workspace", e); -// } - return; - } else { - Node parent = node.getParent(); - setLastModifiedRecursive(parent, event); - } - } - - /** - * Recursively set the last updated time on parents. Useful to use paths when - * dealing with deletions. - */ - protected synchronized void setLastModifiedRecursive(String path, Event event) throws RepositoryException { - // root node will always exist, so end condition is delegated to the other - // recursive setLastModified method - if (session.nodeExists(path)) { - setLastModifiedRecursive(session.getNode(path), event); - } else { - setLastModifiedRecursive(JcrUtils.parentPath(path), event); - } - } - - @Override - public String toString() { - return "Indexer for workspace " + workspaceName + " of repository " + cn; - } - - class IndexingThread extends Thread { - - public IndexingThread() { - super(CmsWorkspaceIndexer.this.toString()); - // TODO Auto-generated constructor stub - } - - @Override - public void run() { - life: while (session != null && session.isLive()) { - try { - Event nextEvent = toProcess.take(); - processEvent(nextEvent); - } catch (InterruptedException e) { - // silent - interrupted(); - } - - if (stopping.get() && toProcess.isEmpty()) { - break life; - } - } - if (log.isDebugEnabled()) - log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down."); - } - - } - -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java deleted file mode 100644 index f2196bd41..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.osgi.DataModelNamespace; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.framework.BundleListener; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWire; -import org.osgi.framework.wiring.BundleWiring; - -class DataModels implements BundleListener { - private final static CmsLog log = CmsLog.getLog(DataModels.class); - - private Map dataModels = new TreeMap<>(); - - public DataModels(BundleContext bc) { - for (Bundle bundle : bc.getBundles()) - processBundle(bundle, null); - bc.addBundleListener(this); - } - - public List getNonAbstractDataModels() { - List 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 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 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 providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); - if (providedDataModels.size() == 0) - return; - List requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); - // process requirements first - for (BundleWire bundleWire : requiredDataModels) { - List 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 required; - - private DataModel(String name, BundleCapability bundleCapability, List requiredDataModels) { - assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace()); - this.name = name; - Map 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 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 getRequired() { - return required; - } - - // @Override - // public int compareTo(DataModel o) { - // if (equals(o)) - // return 0; - // int res = required(this, o, 0); - // if (res != 0) - // return res; - // // the other way round - // res = required(o, this, 0); - // if (res != 0) - // return -res; - // return 0; - // } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof DataModel) - return ((DataModel) obj).name.equals(name); - return false; - } - - @Override - public String toString() { - return "Data model " + name; - } - - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java deleted file mode 100644 index 298025096..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.security.PrivilegedAction; -import java.text.SimpleDateFormat; -import java.util.HashSet; -import java.util.Set; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.security.Privilege; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrRepositoryWrapper; -import org.argeo.jcr.JcrUtils; - -/** - * Make sure each user has a home directory available. - */ -class EgoRepository extends JcrRepositoryWrapper implements KernelConstants { - - /** The home base path. */ -// private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH; -// private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH; -// private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH; - - private Set checkedUsers = new HashSet(); - - 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() { - - @Override - public Void run() { - loginOrCreateWorkspace(defaultHomeWorkspace); - loginOrCreateWorkspace(defaultGroupsWorkspace); - return null; - } - - }); - } - } - - private void loginOrCreateWorkspace(String workspace) { - Session adminSession = null; - try { - adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace); -// JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ); - -// initJcr(adminSession); - } catch (RepositoryException e) { - throw new JcrException("Cannot init JCR home", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - -// @Override -// public Session login(Credentials credentials, String workspaceName) -// throws LoginException, NoSuchWorkspaceException, RepositoryException { -// if (workspaceName == null) { -// return super.login(credentials, getUserHomeWorkspace()); -// } else { -// return super.login(credentials, workspaceName); -// } -// } - - protected String getUserHomeWorkspace() { - // TODO base on JAAS Subject metadata - return defaultHomeWorkspace; - } - - protected String getGroupsWorkspace() { - // TODO base on JAAS Subject metadata - return defaultGroupsWorkspace; - } - -// protected String getGuestsWorkspace() { -// // TODO base on JAAS Subject metadata -// return defaultGuestsWorkspace; -// } - - @Override - protected void processNewSession(Session session, String workspaceName) { - String username = session.getUserID(); - if (username == null || username.toString().equals("")) - return; - if (session.getUserID().equals(CmsConstants.ROLE_ANONYMOUS)) - return; - - String userHomeWorkspace = getUserHomeWorkspace(); - if (workspaceName == null || !workspaceName.equals(userHomeWorkspace)) - return; - - if (checkedUsers.contains(username)) - return; - Session adminSession = KernelUtils.openAdminSession(getRepository(workspaceName), workspaceName); - try { - syncJcr(adminSession, username); - checkedUsers.add(username); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - - /* - * JCR - */ - /** Session is logged out. */ - private void initJcr(Session adminSession) { - try { -// JcrUtils.mkdirs(adminSession, homeBasePath); -// JcrUtils.mkdirs(adminSession, groupsBasePath); - adminSession.save(); - -// JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); -// JcrUtils.addPrivilege(adminSession, groupsBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); - adminSession.save(); - } catch (RepositoryException e) { - throw new CmsException("Cannot initialize home repository", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - - protected synchronized void syncJcr(Session adminSession, String username) { - // only in the default workspace -// if (workspaceName != null) -// return; - // skip system users - if (username.endsWith(CmsConstants.ROLES_BASEDN)) - return; - - try { - Node userHome = CmsJcrUtils.getUserHome(adminSession, username); - if (userHome == null) { -// String homePath = generateUserPath(username); - String userId = extractUserId(username); -// if (adminSession.itemExists(homePath))// duplicate user id -// userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); -// else -// userHome = JcrUtils.mkdirs(adminSession, homePath); - userHome = adminSession.getRootNode().addNode(userId); -// userHome.addMixin(NodeTypes.NODE_USER_HOME); - userHome.addMixin(NodeType.MIX_CREATED); - userHome.addMixin(NodeType.MIX_TITLE); - userHome.setProperty(Property.JCR_ID, username); - // TODO use display name - userHome.setProperty(Property.JCR_TITLE, userId); -// userHome.setProperty(NodeNames.LDAP_UID, username); - adminSession.save(); - - JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username); - JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL); -// JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER, -// Privilege.JCR_READ); - } - if (adminSession.hasPendingChanges()) - adminSession.save(); - } catch (RepositoryException e) { - JcrUtils.discardQuietly(adminSession); - throw new JcrException("Cannot sync node security model for " + username, e); - } - } - - /** Generate path for a new user home */ - private String generateUserPath(String username) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new CmsException("Invalid name " + username, e); - } - String userId = dn.getRdn(dn.size() - 1).getValue().toString(); - return '/' + userId; -// int atIndex = userId.indexOf('@'); -// if (atIndex < 0) { -// return homeBasePath+'/' + userId; -// } else { -// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId; -// } - } - - private String extractUserId(String username) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new CmsException("Invalid name " + username, e); - } - String userId = dn.getRdn(dn.size() - 1).getValue().toString(); - return userId; -// int atIndex = userId.indexOf('@'); -// if (atIndex < 0) { -// return homeBasePath+'/' + userId; -// } else { -// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId; -// } - } - - public void createWorkgroup(LdapName dn) { - String groupsWorkspace = getGroupsWorkspace(); - Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace); - String cn = dn.getRdn(dn.size() - 1).getValue().toString(); - Node newWorkgroup = CmsJcrUtils.getGroupHome(adminSession, cn); - if (newWorkgroup != null) { - JcrUtils.logoutQuietly(adminSession); - throw new CmsException("Workgroup " + newWorkgroup + " already exists for " + dn); - } - try { - // TODO enhance transformation of cn to a valid node name - // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_"); - String relPath = JcrUtils.replaceInvalidChars(cn); - newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED); -// newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED); -// newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME); - newWorkgroup.addMixin(NodeType.MIX_CREATED); - newWorkgroup.addMixin(NodeType.MIX_TITLE); - newWorkgroup.setProperty(Property.JCR_ID, dn.toString()); - newWorkgroup.setProperty(Property.JCR_TITLE, cn); -// newWorkgroup.setProperty(NodeNames.LDAP_CN, cn); - adminSession.save(); - JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL); - adminSession.save(); - } catch (RepositoryException e) { - throw new CmsException("Cannot create workgroup", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - - } - - public boolean isRemote() { - return remote; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java deleted file mode 100644 index bad9fdfd5..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.util.Map; -import java.util.TreeMap; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.jackrabbit.core.RepositoryImpl; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; - -class JackrabbitLocalRepository extends LocalRepository { - private final static CmsLog log = CmsLog.getLog(JackrabbitLocalRepository.class); - final String SECURITY_WORKSPACE = "security"; - - private Map workspaceMonitors = new TreeMap<>(); - - public JackrabbitLocalRepository(RepositoryImpl repository, String cn) { - super(repository, cn); -// Session session = KernelUtils.openAdminSession(repository); -// try { -// if (NodeConstants.NODE.equals(cn)) -// for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) { -// addMonitor(workspaceName); -// } -// } catch (RepositoryException e) { -// throw new IllegalStateException(e); -// } finally { -// JcrUtils.logoutQuietly(session); -// } - } - - protected RepositoryImpl getJackrabbitrepository(String workspaceName) { - return (RepositoryImpl) getRepository(workspaceName); - } - - @Override - protected synchronized void processNewSession(Session session, String workspaceName) { -// String realWorkspaceName = session.getWorkspace().getName(); -// addMonitor(realWorkspaceName); - } - - private void addMonitor(String realWorkspaceName) { - if (realWorkspaceName.equals(SECURITY_WORKSPACE)) - return; - if (!CmsConstants.NODE_REPOSITORY.equals(getCn())) - return; - - if (!workspaceMonitors.containsKey(realWorkspaceName)) { - try { - CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer( - getJackrabbitrepository(realWorkspaceName), getCn(), realWorkspaceName); - workspaceMonitors.put(realWorkspaceName, workspaceMonitor); - workspaceMonitor.init(); - if (log.isDebugEnabled()) - log.debug("Registered " + workspaceMonitor); - } catch (RepositoryException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - public void destroy() { - for (String workspaceName : workspaceMonitors.keySet()) { - workspaceMonitors.get(workspaceName).destroy(); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java deleted file mode 100644 index 17625f5d2..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java +++ /dev/null @@ -1,397 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.ByteArrayInputStream; -import java.io.CharArrayReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.query.Query; - -import org.apache.commons.io.IOUtils; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.AbstractKeyring; -import org.argeo.cms.security.PBEKeySpecCallback; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -/** JCR based implementation of a keyring */ -public class JcrKeyring extends AbstractKeyring implements ArgeoNames { - private final static CmsLog log = CmsLog.getLog(JcrKeyring.class); - /** - * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case - */ - public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l; - public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; - public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; - public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; - - private Integer iterationCountFactor = 200; - private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH; - private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; - private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; - private String cipherName = DEFAULT_CIPHER_NAME; - - private final Repository repository; - // TODO remove thread local session ; open a session each time - private ThreadLocal sessionThreadLocal = new ThreadLocal() { - - @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 notYetSavedKeyring = new ThreadLocal() { - // - // @Override - // protected Node initialValue() { - // return null; - // } - // }; - - public JcrKeyring(Repository repository) { - this.repository = repository; - } - - private Session session() { - Session session = this.sessionThreadLocal.get(); - if (!session.isLive()) { - session = login(); - sessionThreadLocal.set(session); - } - return session; - } - - private Session login() { - try { - return repository.login(CmsConstants.HOME_WORKSPACE); - } catch (RepositoryException e) { - throw new JcrException("Cannot login key ring session", e); - } - } - - @Override - protected synchronized Boolean isSetup() { - Session session = null; - try { - // if (notYetSavedKeyring.get() != null) - // return true; - session = session(); - session.refresh(true); - Node userHome = CmsJcrUtils.getUserHome(session); - return userHome.hasNode(ARGEO_KEYRING); - } catch (RepositoryException e) { - throw new JcrException("Cannot check whether keyring is setup", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - @Override - protected synchronized void setup(char[] password) { - Binary binary = null; - // InputStream in = null; - try { - session().refresh(true); - Node userHome = CmsJcrUtils.getUserHome(session()); - Node keyring; - if (userHome.hasNode(ARGEO_KEYRING)) { - throw new IllegalArgumentException("Keyring already set up"); - } else { - keyring = userHome.addNode(ARGEO_KEYRING); - } - keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC); - - // deterministic salt and iteration count based on username - String username = session().getUserID(); - byte[] salt = new byte[8]; - byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8); - for (int i = 0; i < salt.length; i++) { - if (i < usernameBytes.length) - salt[i] = usernameBytes[i]; - else - salt[i] = 0; - } - try (InputStream in = new ByteArrayInputStream(salt);) { - binary = session().getValueFactory().createBinary(in); - keyring.setProperty(ARGEO_SALT, binary); - } catch (IOException e) { - throw new RuntimeException("Cannot set keyring salt", e); - } - - Integer iterationCount = username.length() * iterationCountFactor; - keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount); - - // default algo - // TODO check if algo and key length are available, use DES if not - keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName); - keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength); - keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption); - keyring.setProperty(ARGEO_CIPHER, cipherName); - - keyring.getSession().save(); - - // encrypted password hash - // IOUtils.closeQuietly(in); - // JcrUtils.closeQuietly(binary); - // byte[] btPass = hash(password, salt, iterationCount); - // in = new ByteArrayInputStream(btPass); - // binary = session().getValueFactory().createBinary(in); - // keyring.setProperty(ARGEO_PASSWORD, binary); - - // notYetSavedKeyring.set(keyring); - } catch (RepositoryException e) { - throw new JcrException("Cannot setup keyring", e); - } finally { - JcrUtils.closeQuietly(binary); - // IOUtils.closeQuietly(in); - // JcrUtils.discardQuietly(session()); - } - } - - @Override - protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) { - Session session = null; - try { - session = session(); - session.refresh(true); - Node userHome = CmsJcrUtils.getUserHome(session); - Node keyring; - if (userHome.hasNode(ARGEO_KEYRING)) - keyring = userHome.getNode(ARGEO_KEYRING); - // else if (notYetSavedKeyring.get() != null) - // keyring = notYetSavedKeyring.get(); - else - throw new IllegalStateException("Keyring not setup"); - - pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY).getString(), - JcrUtils.getBinaryAsBytes(keyring.getProperty(ARGEO_SALT)), - (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(), - (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(), - keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString()); - - // if (notYetSavedKeyring.get() != null) - // notYetSavedKeyring.remove(); - } catch (RepositoryException e) { - throw new JcrException("Cannot handle key spec callback", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - /** The parent node must already exist at this path. */ - @Override - protected synchronized void encrypt(String path, InputStream unencrypted) { - // should be called first for lazy initialization - SecretKey secretKey = getSecretKey(null); - Cipher cipher = createCipher(); - - // Binary binary = null; - // InputStream in = null; - try { - session().refresh(true); - Node node; - if (!session().nodeExists(path)) { - String parentPath = JcrUtils.parentPath(path); - if (!session().nodeExists(parentPath)) - throw new IllegalStateException("No parent node of " + path); - Node parentNode = session().getNode(parentPath); - node = parentNode.addNode(JcrUtils.nodeNameFromPath(path)); - } else { - node = session().getNode(path); - } - encrypt(secretKey, cipher, node, unencrypted); - // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); - // SecureRandom random = new SecureRandom(); - // byte[] iv = new byte[16]; - // random.nextBytes(iv); - // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); - // - // try (InputStream in = new CipherInputStream(unencrypted, cipher);) { - // binary = session().getValueFactory().createBinary(in); - // node.setProperty(Property.JCR_DATA, binary); - // session().save(); - // } - } catch (RepositoryException e) { - throw new JcrException("Cannot encrypt", e); - } finally { - try { - unencrypted.close(); - } catch (IOException e) { - // silent - } - // IOUtils.closeQuietly(unencrypted); - // IOUtils.closeQuietly(in); - // JcrUtils.closeQuietly(binary); - JcrUtils.logoutQuietly(session()); - } - } - - protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) { - try { - node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); - SecureRandom random = new SecureRandom(); - byte[] iv = new byte[16]; - random.nextBytes(iv); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); - - Binary binary = null; - try (InputStream in = new CipherInputStream(unencrypted, cipher);) { - binary = session().getValueFactory().createBinary(in); - node.setProperty(Property.JCR_DATA, binary); - session().save(); - } finally { - JcrUtils.closeQuietly(binary); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot encrypt", e); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Cannot encrypt", e); - } - } - - @Override - protected synchronized InputStream decrypt(String path) { - Binary binary = null; - try { - session().refresh(true); - if (!session().nodeExists(path)) { - char[] password = ask(); - Reader reader = new CharArrayReader(password); - return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8)); - } else { - // should be called first for lazy initialisation - SecretKey secretKey = getSecretKey(null); - Cipher cipher = createCipher(); - Node node = session().getNode(path); - return decrypt(secretKey, cipher, node); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot decrypt", e); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Cannot decrypt", e); - } finally { - JcrUtils.closeQuietly(binary); - JcrUtils.logoutQuietly(session()); - } - } - - protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node) - throws RepositoryException, GeneralSecurityException { - if (node.hasProperty(ARGEO_IV)) { - byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV)); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); - } else { - cipher.init(Cipher.DECRYPT_MODE, secretKey); - } - - Binary binary = node.getProperty(Property.JCR_DATA).getBinary(); - InputStream encrypted = binary.getStream(); - return new CipherInputStream(encrypted, cipher); - } - - protected Cipher createCipher() { - try { - Node userHome = CmsJcrUtils.getUserHome(session()); - if (!userHome.hasNode(ARGEO_KEYRING)) - throw new IllegalArgumentException("Keyring not setup"); - Node keyring = userHome.getNode(ARGEO_KEYRING); - String cipherName = keyring.getProperty(ARGEO_CIPHER).getString(); - Provider securityProvider = getSecurityProvider(); - Cipher cipher; - if (securityProvider == null)// TODO use BC? - cipher = Cipher.getInstance(cipherName); - else - cipher = Cipher.getInstance(cipherName, securityProvider); - return cipher; - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalArgumentException("Cannot get cipher", e); - } catch (RepositoryException e) { - throw new JcrException("Cannot get cipher", e); - } finally { - - } - } - - public synchronized void changePassword(char[] oldPassword, char[] newPassword) { - // TODO make it XA compatible - SecretKey oldSecretKey = getSecretKey(oldPassword); - SecretKey newSecretKey = getSecretKey(newPassword); - Session session = session(); - try { - NodeIterator encryptedNodes = session.getWorkspace().getQueryManager() - .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes(); - while (encryptedNodes.hasNext()) { - Node node = encryptedNodes.nextNode(); - InputStream in = decrypt(oldSecretKey, createCipher(), node); - encrypt(newSecretKey, createCipher(), node, in); - if (log.isDebugEnabled()) - log.debug("Converted keyring encrypted value of " + node.getPath()); - } - } catch (GeneralSecurityException e) { - throw new RuntimeException("Cannot change JCR keyring password", e); - } catch (RepositoryException e) { - throw new JcrException("Cannot change JCR keyring password", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - // public synchronized void setSession(Session session) { - // this.session = session; - // } - - public void setIterationCountFactor(Integer iterationCountFactor) { - this.iterationCountFactor = iterationCountFactor; - } - - public void setSecretKeyLength(Long keyLength) { - this.secretKeyLength = keyLength; - } - - public void setSecretKeyFactoryName(String secreteKeyFactoryName) { - this.secretKeyFactoryName = secreteKeyFactoryName; - } - - public void setSecretKeyEncryption(String secreteKeyEncryption) { - this.secretKeyEncryption = secreteKeyEncryption; - } - - public void setCipherName(String cipherName) { - this.cipherName = cipherName; - } - -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java deleted file mode 100644 index 342c1add7..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java +++ /dev/null @@ -1,191 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.jcr.RepoConf; -import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** - * OSGi-aware Jackrabbit repository factory which can retrieve/publish - * {@link Repository} as OSGi services. - */ -public class JcrRepositoryFactory implements RepositoryFactory { - private final CmsLog log = CmsLog.getLog(getClass()); -// private final BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - // private Resource fileRepositoryConfiguration = new ClassPathResource( - // "/org/argeo/cms/internal/kernel/repository-localfs.xml"); - - protected Repository getRepositoryByAlias(String alias) { - BundleContext bundleContext = CmsJcrActivator.getBundleContext(); - if (bundleContext != null) { - try { - Collection> 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 props = new Hashtable(); - // 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 params = new HashMap(); - params.put(KernelConstants.JACKRABBIT_REPOSITORY_URI, uri); - if (defaultWorkspace != null) - params.put(KernelConstants.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, defaultWorkspace); - Repository repository = new Jcr2davRepositoryFactory().getRepository(params); - if (repository == null) - throw new IllegalArgumentException("Remote Davex repository " + uri + " not found"); - log.info("Initialized remote Jackrabbit repository from uri " + uri); - return repository; - } - - @SuppressWarnings({ "rawtypes" }) - protected Repository createFileRepository(final String uri, Map parameters) throws RepositoryException { - throw new UnsupportedOperationException(); - // InputStream configurationIn = null; - // try { - // Properties vars = new Properties(); - // vars.putAll(parameters); - // String dirPath = uri.substring("file:".length()); - // File homeDir = new File(dirPath); - // if (homeDir.exists() && !homeDir.isDirectory()) - // throw new ArgeoJcrException("Repository home " + dirPath + " is not a - // directory"); - // if (!homeDir.exists()) - // homeDir.mkdirs(); - // configurationIn = fileRepositoryConfiguration.getInputStream(); - // vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, - // homeDir.getCanonicalPath()); - // RepositoryConfig repositoryConfig = RepositoryConfig.create(new - // InputSource(configurationIn), vars); - // - // // TransientRepository repository = new - // // TransientRepository(repositoryConfig); - // final RepositoryImpl repository = - // RepositoryImpl.create(repositoryConfig); - // Session session = repository.login(); - // // FIXME make it generic - // org.argeo.jcr.JcrUtils.addPrivilege(session, "/", "ROLE_ADMIN", - // "jcr:all"); - // org.argeo.jcr.JcrUtils.logoutQuietly(session); - // Runtime.getRuntime().addShutdownHook(new Thread("Clean JCR repository - // " + uri) { - // public void run() { - // repository.shutdown(); - // log.info("Destroyed repository " + uri); - // } - // }); - // log.info("Initialized file Jackrabbit repository from uri " + uri); - // return repository; - // } catch (Exception e) { - // throw new ArgeoJcrException("Cannot create repository " + uri, e); - // } finally { - // IOUtils.closeQuietly(configurationIn); - // } - } - - protected String getAliasFromURI(String uri) { - try { - URI uriObj = new URI(uri); - String alias = uriObj.getPath(); - if (alias.charAt(0) == '/') - alias = alias.substring(1); - if (alias.charAt(alias.length() - 1) == '/') - alias = alias.substring(0, alias.length() - 1); - return alias; - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot interpret URI " + uri, e); - } - } - - /** - * Called after the repository has been initialised. Does nothing by default. - */ - @SuppressWarnings("rawtypes") - protected void postInitialization(Repository repository, Map parameters) { - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java deleted file mode 100644 index 93f29fbe8..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import org.argeo.api.cms.CmsConstants; - -/** Internal CMS constants. */ -@Deprecated -public interface KernelConstants { - // Directories - String DIR_NODE = "node"; - String DIR_REPOS = "repos"; - String DIR_INDEXES = "indexes"; - String DIR_TRANSACTIONS = "transactions"; - - // Files - String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif"; - String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12"; - String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key"; - String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt"; - String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab"; - - // Security - String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg"; - String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg"; - - // Java - String JAAS_CONFIG_PROP = "java.security.auth.login.config"; - - // DEFAULTS JCR PATH - String DEFAULT_HOME_BASE_PATH = "/home"; - String DEFAULT_USERS_BASE_PATH = "/users"; - String DEFAULT_GROUPS_BASE_PATH = "/groups"; - - // KERBEROS - String DEFAULT_KERBEROS_SERVICE = "HTTP"; - - // HTTP client - String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility"; - - // RWT / RAP - // String PATH_WORKBENCH = "/ui"; - // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public"; - - String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config"; - String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern"; - // default Jetty server configured via JettyConfigurator - String DEFAULT_JETTY_SERVER = "default"; - String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; - - // avoid dependencies - String CONTEXT_NAME_PROP = "contextName"; - String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; - String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java deleted file mode 100644 index edfe87a03..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java +++ /dev/null @@ -1,262 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.PrivilegedAction; -import java.security.URIParameter; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.Properties; -import java.util.TreeMap; -import java.util.TreeSet; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; -import org.argeo.cms.osgi.DataModelNamespace; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - -/** Package utilities */ -class KernelUtils implements KernelConstants { - final static String OSGI_INSTANCE_AREA = "osgi.instance.area"; - final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; - - static void setJaasConfiguration(URL jaasConfigurationUrl) { - try { - URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI()); - javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration - .getInstance("JavaLoginConfig", uriParameter); - javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e); - } - } - - static Dictionary asDictionary(Properties props) { - Hashtable hashtable = new Hashtable(); - for (Object key : props.keySet()) { - hashtable.put(key.toString(), props.get(key)); - } - return hashtable; - } - - static Dictionary 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(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 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() { - - @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() { - - @Override - public Void run() { - try { - action.run(); - return null; - } finally { - try { - loginContext.logout(); - } catch (LoginException e) { - throw new IllegalStateException(e); - } - } - } - - }); - } - - static void asyncOpen(ServiceTracker st) { - Runnable run = new Runnable() { - - @Override - public void run() { - st.open(); - } - }; -// Activator.getInternalExecutorService().execute(run); - new Thread(run, "Open service tracker " + st).start(); - } - - static BundleContext getBundleContext() { - return CmsJcrActivator.getBundleContext(); - } - - static boolean asBoolean(String value) { - if (value == null) - return false; - switch (value) { - case "true": - return true; - case "false": - return false; - default: - throw new IllegalArgumentException( - "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value); - } - } - - private static URI safeUri(String uri) { - if (uri == null) - throw new IllegalArgumentException("URI cannot be null"); - try { - return new URI(uri); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Badly formatted URI " + uri, e); - } - } - - private KernelUtils() { - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java deleted file mode 100644 index 0bac94cc0..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import javax.jcr.Repository; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.jcr.JcrRepositoryWrapper; - -class LocalRepository extends JcrRepositoryWrapper { - private final String cn; - - public LocalRepository(Repository repository, String cn) { - super(repository); - this.cn = cn; - // Map attrs = dataModelCapability.getAttributes(); - // cn = (String) attrs.get(DataModelNamespace.NAME); - putDescriptor(CmsConstants.CN, cn); - } - - String getCn() { - return cn; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java deleted file mode 100644 index 9cd1f7269..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.util.Dictionary; - -import javax.jcr.Repository; - -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; - -class NodeKeyRing extends JcrKeyring implements ManagedService{ - - public NodeKeyRing(Repository repository) { - super(repository); - } - - @Override - public void updated(Dictionary properties) throws ConfigurationException { - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java deleted file mode 100644 index e05a0023e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.jcr.RepoConf; -import org.argeo.cms.internal.jcr.RepositoryBuilder; -import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; -import org.argeo.util.LangUtils; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedServiceFactory; - -/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */ -public class RepositoryContextsFactory implements ManagedServiceFactory { - private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class); -// private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext(); - - private Map repositories = new HashMap(); - private Map pidToCn = new HashMap(); - - 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 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 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 parameters = new HashMap(); - 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 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 homeProps = LangUtils.dict(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); - EgoRepository homeRepository = new EgoRepository(repository, true); - CmsJcrActivator.registerService(Repository.class, homeRepository, homeProps); - } - } - } catch (RepositoryException | URISyntaxException | IOException e) { - throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e); - } - - } - - @Override - public void deleted(String pid) { - RepositoryContext repositoryContext = repositories.remove(pid); - repositoryContext.getRepository().shutdown(); - if (log.isDebugEnabled()) - log.debug("Deleted repository " + pid); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java deleted file mode 100644 index 5a2cd5b7b..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.File; -import java.lang.management.ManagementFactory; - -import org.apache.jackrabbit.api.stats.RepositoryStatistics; -import org.apache.jackrabbit.stats.RepositoryStatisticsImpl; -import org.argeo.api.cms.CmsLog; - -/** - * Background thread started by the kernel, which gather statistics and - * monitor/control other processes. - */ -public class StatisticsThread extends Thread { - private final static CmsLog log = CmsLog.getLog(StatisticsThread.class); - - private RepositoryStatisticsImpl repoStats; - - /** The smallest period of operation, in ms */ - private final long PERIOD = 60 * 1000l; - /** One ms in ns */ - private final static long m = 1000l * 1000l; - private final static long M = 1024l * 1024l; - - private boolean running = true; - - private CmsLog kernelStatsLog = CmsLog.getLog("argeo.stats.kernel"); - private CmsLog nodeStatsLog = CmsLog.getLog("argeo.stats.node"); - - @SuppressWarnings("unused") - private long cycle = 0l; - - public StatisticsThread(String name) { - super(name); - } - - private void doSmallestPeriod() { - // Clean expired sessions - // FIXME re-enable it in CMS - //CmsSessionImpl.closeInvalidSessions(); - - if (kernelStatsLog.isDebugEnabled()) { - StringBuilder line = new StringBuilder(64); - line.append("§\t"); - long freeMem = Runtime.getRuntime().freeMemory() / M; - long totalMem = Runtime.getRuntime().totalMemory() / M; - long maxMem = Runtime.getRuntime().maxMemory() / M; - double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); - // in min - boolean min = true; - long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / (1000 * 60); - if (uptime > 24 * 60) { - min = false; - uptime = uptime / 60; - } - line.append(uptime).append(min ? " min" : " h").append('\t'); - line.append(loadAvg).append('\t').append(maxMem).append('\t').append(totalMem).append('\t').append(freeMem) - .append('\t'); - kernelStatsLog.debug(line); - } - - if (nodeStatsLog.isDebugEnabled()) { - File dataDir = KernelUtils.getOsgiInstanceDir(); - long freeSpace = dataDir.getUsableSpace() / M; - // File currentRoot = null; - // for (File root : File.listRoots()) { - // String rootPath = root.getAbsolutePath(); - // if (dataDir.getAbsolutePath().startsWith(rootPath)) { - // if (currentRoot == null - // || (rootPath.length() > currentRoot.getPath() - // .length())) { - // currentRoot = root; - // } - // } - // } - // long totalSpace = currentRoot.getTotalSpace(); - StringBuilder line = new StringBuilder(128); - line.append("§\t").append(freeSpace).append(" MB left in " + dataDir); - line.append('\n'); - if (repoStats != null) - for (RepositoryStatistics.Type type : RepositoryStatistics.Type.values()) { - long[] vals = repoStats.getTimeSeries(type).getValuePerMinute(); - long val = vals[vals.length - 1]; - line.append(type.name()).append('\t').append(val).append('\n'); - } - nodeStatsLog.debug(line); - } - } - - @Override - public void run() { - if (log.isTraceEnabled()) - log.trace("Kernel thread started."); - final long periodNs = PERIOD * m; - while (running) { - long beginNs = System.nanoTime(); - doSmallestPeriod(); - - long waitNs = periodNs - (System.nanoTime() - beginNs); - if (waitNs < 0) - continue; - // wait - try { - sleep(waitNs / m, (int) (waitNs % m)); - } catch (InterruptedException e) { - // silent - } - cycle++; - } - } - - public synchronized void destroyAndJoin() { - running = false; - notifyAll(); -// interrupt(); -// try { -// join(PERIOD * 2); -// } catch (InterruptedException e) { -// // throw new CmsException("Kernel thread destruction was interrupted"); -// log.error("Kernel thread destruction was interrupted", e); -// } - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java deleted file mode 100644 index 57860d84f..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.argeo.cms.jcr.internal.osgi; - -import java.util.Dictionary; - -import org.argeo.cms.jcr.internal.StatisticsThread; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -public class CmsJcrActivator implements BundleActivator { - private static BundleContext bundleContext; - -// private List 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 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 void registerService(Class clss, T service, Dictionary properties) { - if (bundleContext != null) { - bundleContext.registerService(clss, service, properties); - } - - } - - @Deprecated - public static BundleContext getBundleContext() { - return bundleContext; - } - - @Deprecated - public static T getService(Class clss) { - if (bundleContext != null) { - return bundleContext.getService(bundleContext.getServiceReference(clss)); - } else { - return null; - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java deleted file mode 100644 index fa3f87f67..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.util.Map; - -import javax.jcr.Repository; - -import org.apache.jackrabbit.server.SessionProvider; -import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; -import org.argeo.api.cms.CmsConstants; - -/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */ -public class CmsRemotingServlet extends JcrRemotingServlet { - private static final long serialVersionUID = 6459455509684213633L; - private Repository repository; - private SessionProvider sessionProvider; - - public CmsRemotingServlet() { - } - - public CmsRemotingServlet(String alias, Repository repository) { - this.repository = repository; - this.sessionProvider = new CmsSessionProvider(alias); - } - - @Override - public Repository getRepository() { - return repository; - } - - public void setRepository(Repository repository, Map properties) { - this.repository = repository; - String alias = properties.get(CmsConstants.CN); - if (alias != null) - sessionProvider = new CmsSessionProvider(alias); - else - throw new IllegalArgumentException("Only aliased repositories are supported"); - } - - @Override - protected SessionProvider getSessionProvider() { - return sessionProvider; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java deleted file mode 100644 index 4e067eea2..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.io.Serializable; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import org.apache.jackrabbit.server.SessionProvider; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsSession; -import org.argeo.cms.auth.RemoteAuthUtils; -import org.argeo.cms.servlet.ServletHttpRequest; -import org.argeo.jcr.JcrUtils; - -/** - * Implements an open session in view patter: a new JCR session is created for - * each request - */ -public class CmsSessionProvider implements SessionProvider, Serializable { - private static final long serialVersionUID = -1358136599534938466L; - - private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class); - - private final String alias; - - private LinkedHashMap 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 dataSessions = new HashMap<>(); - private Set dataSessionsInUse = new HashSet<>(); - private Set 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() { - @Override - public Session run() throws Exception { - return repository.login(workspace); - } - }); - } catch (PrivilegedActionException e) { - throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e); - } - } - - public synchronized void releaseDataSession(String cn, Session session) { - if (additionalDataSessions.contains(session)) { - JcrUtils.logoutQuietly(session); - additionalDataSessions.remove(session); - if (log.isTraceEnabled()) - log.trace("Remove additional data session " + session); - return; - } - String path = cn + '/' + session.getWorkspace().getName(); - if (!dataSessionsInUse.contains(path)) - log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn()); - dataSessionsInUse.remove(path); - Session registeredSession = dataSessions.get(path); - if (session != registeredSession) - log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn()); - if (log.isTraceEnabled()) - log.trace("Released data session " + session + " for " + path); - notifyAll(); - } - - private void checkValid() { - if (!cmsSession.isValid()) - throw new IllegalStateException( - "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd()); - } - - protected void close() { - synchronized (this) { - // TODO check data session in use ? - for (String path : dataSessions.keySet()) - JcrUtils.logoutQuietly(dataSessions.get(path)); - for (Session session : additionalDataSessions) - JcrUtils.logoutQuietly(session); - } - } - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java deleted file mode 100644 index 0f0858f51..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.util.Map; - -import javax.jcr.Repository; - -import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; -import org.argeo.api.cms.CmsConstants; - -/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */ -public class CmsWebDavServlet extends SimpleWebdavServlet { - private static final long serialVersionUID = 7485800288686328063L; - private Repository repository; - - public CmsWebDavServlet() { - } - - public CmsWebDavServlet(String alias, Repository repository) { - this.repository = repository; - setSessionProvider(new CmsSessionProvider(alias)); - } - - @Override - public Repository getRepository() { - return repository; - } - - public void setRepository(Repository repository, Map properties) { - this.repository = repository; - String alias = properties.get(CmsConstants.CN); - if (alias != null) - setSessionProvider(new CmsSessionProvider(alias)); - else - throw new IllegalArgumentException("Only aliased repositories are supported"); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java deleted file mode 100644 index 2f60e97d9..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import org.argeo.cms.servlet.CmsServletContext; - -/** Internal subclass, so that config resources can be loaded from our bundle. */ -public class DataServletContext extends CmsServletContext { - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java deleted file mode 100644 index 11e903db8..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsLog; - -public class JcrHttpUtils { - public final static String HEADER_AUTHORIZATION = "Authorization"; - public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - - public final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml"; - public final static String WEBDAV_CONFIG = "/org/argeo/cms/jcr/internal/servlet/webdav-config.xml"; - - static boolean isBrowser(String userAgent) { - return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") - || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") - || userAgent.contains("opera") || userAgent.contains("browser"); - } - - public static void logResponseHeaders(CmsLog log, HttpServletResponse response) { - if (!log.isDebugEnabled()) - return; - for (String headerName : response.getHeaderNames()) { - Object headerValue = response.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - } - - public static void logRequestHeaders(CmsLog log, HttpServletRequest request) { - if (!log.isDebugEnabled()) - return; - for (Enumeration 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 en = request.getHeaderNames(); - while (en.hasMoreElements()) { - String header = en.nextElement(); - Enumeration values = request.getHeaders(header); - while (values.hasMoreElements()) - buf.append(" " + header + ": " + values.nextElement()); - buf.append('\n'); - } - - // attributed - Enumeration an = request.getAttributeNames(); - while (an.hasMoreElements()) { - String attr = an.nextElement(); - Object value = request.getAttribute(attr); - buf.append(" " + attr + ": " + value); - buf.append('\n'); - } - log.debug("\n" + buf); - } - - private JcrHttpUtils() { - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java deleted file mode 100644 index 21046f34e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import org.argeo.cms.servlet.PrivateWwwAuthServletContext; - -/** Internal subclass, so that config resources can be loaded from our bundle. */ -public class JcrServletContext extends PrivateWwwAuthServletContext { - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java deleted file mode 100644 index 62cdc5f6b..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import static javax.jcr.Property.JCR_DESCRIPTION; -import static javax.jcr.Property.JCR_LAST_MODIFIED; -import static javax.jcr.Property.JCR_TITLE; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.PrivilegedExceptionAction; -import java.util.Calendar; -import java.util.Collection; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.JcrUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; - -public class LinkServlet extends HttpServlet { - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - private static final long serialVersionUID = 3749990143146845708L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - String path = request.getPathInfo(); - String userAgent = request.getHeader("User-Agent").toLowerCase(); - boolean isBot = false; - // boolean isCompatibleBrowser = false; - if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) { - isBot = true; - } - // else if (userAgent.contains("webkit") || - // userAgent.contains("gecko") || userAgent.contains("firefox") - // || userAgent.contains("msie") || userAgent.contains("chrome") || - // userAgent.contains("chromium") - // || userAgent.contains("opera") || userAgent.contains("browser")) - // { - // isCompatibleBrowser = true; - // } - - if (isBot) { - // log.warn("# BOT " + request.getHeader("User-Agent")); - canonicalAnswer(request, response, path); - return; - } - - // if (isCompatibleBrowser && log.isTraceEnabled()) - // log.trace("# BWS " + request.getHeader("User-Agent")); - redirectTo(response, "/#" + path); - } - - private void redirectTo(HttpServletResponse response, String location) { - response.setHeader("Location", location); - response.setStatus(HttpServletResponse.SC_FOUND); - } - - // private boolean canonicalAnswerNeededBy(HttpServletRequest request) { - // String userAgent = request.getHeader("User-Agent").toLowerCase(); - // return userAgent.startsWith("facebookexternalhit/"); - // } - - /** For bots which don't understand RWT. */ - private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) { - Session session = null; - try { - PrintWriter writer = response.getWriter(); - session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction() { - - @Override - public Session run() throws Exception { - Collection> 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(""); - buf.append(""); - writeMeta(buf, "og:title", escapeHTML(title)); - writeMeta(buf, "og:type", "website"); - buf.append(""); - buf.append(""); - 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(""); - buf.append(""); - buf.append("

!! This page is meant for indexing robots, not for real people," + " visit ").append(escapeHTML(title)).append(" instead.

"); - writeCanonical(buf, node); - buf.append(""); - buf.append(""); - 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(""); - } - - private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException { - buf.append("
"); - if (node.hasProperty(JCR_TITLE)) - buf.append("

").append(node.getProperty(JCR_TITLE).getString()).append("

"); - if (node.hasProperty(JCR_DESCRIPTION)) - buf.append("

").append(node.getProperty(JCR_DESCRIPTION).getString()).append("

"); - NodeIterator children = node.getNodes(); - while (children.hasNext()) { - writeCanonical(buf, children.nextNode()); - } - buf.append("
"); - } - - // DATA - private StringBuilder getServerBaseUrl(HttpServletRequest request) { - try { - URL url = new URL(request.getRequestURL().toString()); - StringBuilder buf = new StringBuilder(); - buf.append(url.getProtocol()).append("://").append(url.getHost()); - if (url.getPort() != -1) - buf.append(':').append(url.getPort()); - return buf; - } catch (MalformedURLException e) { - throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); - } - } - - private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node)); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - } - - // public static String getDataPath(Node node) throws - // RepositoryException { - // assert node != null; - // String userId = node.getSession().getUserID(); - //// if (log.isTraceEnabled()) - //// log.trace(userId + " : " + node.getPath()); - // StringBuilder buf = new StringBuilder(); - // boolean isAnonymous = - // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS); - // if (isAnonymous) - // buf.append(WEBDAV_PUBLIC); - // else - // buf.append(WEBDAV_PRIVATE); - // Session session = node.getSession(); - // Repository repository = session.getRepository(); - // String cn; - // if (repository.isSingleValueDescriptor(NodeConstants.CN)) { - // cn = repository.getDescriptor(NodeConstants.CN); - // } else { - //// log.warn("No cn defined in repository, using " + - // NodeConstants.NODE); - // cn = NodeConstants.NODE; - // } - // return - // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath()) - // .toString(); - // } - - private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append('/').append('!').append(node.getPath()); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - // return request.getRequestURL().append('!').append(node.getPath()) - // .toString(); - } - - private Subject anonymousLogin() { - Subject subject = new Subject(); - LoginContext lc; - try { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject); - lc.login(); - return subject; - } catch (LoginException e) { - throw new CmsException("Cannot login as anonymous", e); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml deleted file mode 100644 index 59f22cd5e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml deleted file mode 100644 index 436389898..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - nt:file - nt:resource - - - - - - - - - - - - - rep - jcr - - node - argeo - cms - slc - connect - activities - people - documents - tracker - - - - - - - diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd deleted file mode 100644 index a2306c60e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd +++ /dev/null @@ -1 +0,0 @@ - diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd deleted file mode 100644 index d8a26b64e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd +++ /dev/null @@ -1,9 +0,0 @@ - - -[node:userHome] -mixin -- ldap:uid (STRING) m - -[node:groupHome] -mixin -- ldap:cn (STRING) m diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java deleted file mode 100644 index ccd543f4d..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.jcr.tabular; - -import java.io.OutputStream; - -import org.argeo.cms.tabular.TabularWriter; -import org.argeo.util.CsvWriter; - -/** Write tabular content in a stream as CSV. Wraps a {@link CsvWriter}. */ -public class CsvTabularWriter implements TabularWriter { - private CsvWriter csvWriter; - - public CsvTabularWriter(OutputStream out) { - this.csvWriter = new CsvWriter(out); - } - - public void appendRow(Object[] row) { - csvWriter.writeLine(row); - } - - public void close() { - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java deleted file mode 100644 index d1d9b583b..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.argeo.cms.jcr.tabular; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.tabular.ArrayTabularRow; -import org.argeo.cms.tabular.TabularColumn; -import org.argeo.cms.tabular.TabularRow; -import org.argeo.cms.tabular.TabularRowIterator; -import org.argeo.jcr.JcrException; -import org.argeo.util.CsvParser; - -/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */ -public class JcrTabularRowIterator implements TabularRowIterator { - private Boolean hasNext = null; - private Boolean parsingCompleted = false; - - private Long currentRowNumber = 0l; - - private List header = new ArrayList(); - - /** referenced so that we can close it */ - private Binary binary; - private InputStream in; - - private CsvParser csvParser; - private ArrayBlockingQueue> 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>(1000); - csvParser = new CsvParser() { - protected void processLine(Integer lineNumber, - List header, List 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 tokens = textLines.take(); - List objs = new ArrayList(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 getHeader() { - return header; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java deleted file mode 100644 index cc3e0d7a9..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.jcr.tabular; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.List; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.tabular.TabularColumn; -import org.argeo.cms.tabular.TabularWriter; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.CsvWriter; - -/** Write / reference tabular content in a JCR repository. */ -public class JcrTabularWriter implements TabularWriter { - private Node contentNode; - private ByteArrayOutputStream out; - private CsvWriter csvWriter; - - @SuppressWarnings("unused") - private final List columns; - - /** Creates a table node */ - public JcrTabularWriter(Node tableNode, List columns, - String contentNodeType) { - try { - this.columns = columns; - for (TabularColumn column : columns) { - String normalized = JcrUtils.replaceInvalidChars(column - .getName()); - Node columnNode = tableNode.addNode(normalized, - ArgeoTypes.ARGEO_COLUMN); - columnNode.setProperty(Property.JCR_TITLE, column.getName()); - if (column.getType() != null) - columnNode.setProperty(Property.JCR_REQUIRED_TYPE, - PropertyType.nameFromValue(column.getType())); - else - columnNode.setProperty(Property.JCR_REQUIRED_TYPE, - PropertyType.TYPENAME_STRING); - } - contentNode = tableNode.addNode(Property.JCR_CONTENT, - contentNodeType); - if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) { - contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv"); - contentNode.setProperty(Property.JCR_ENCODING, "UTF-8"); - out = new ByteArrayOutputStream(); - csvWriter = new CsvWriter(out); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot create table node " + tableNode, e); - } - } - - public void appendRow(Object[] row) { - csvWriter.writeLine(row); - } - - public void close() { - Binary binary = null; - InputStream in = null; - try { - // TODO parallelize with pipes and writing from another thread - in = new ByteArrayInputStream(out.toByteArray()); - binary = contentNode.getSession().getValueFactory() - .createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - } catch (RepositoryException e) { - throw new JcrException("Cannot store data in " + contentNode, e); - } finally { - IOUtils.closeQuietly(in); - JcrUtils.closeQuietly(binary); - } - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java deleted file mode 100644 index 506a6ac98..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */ -package org.argeo.cms.jcr.tabular; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java deleted file mode 100644 index 7396c87e7..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java +++ /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 sharedState, Map options) { - this.subject = subject; - } - - @Override - public boolean login() throws LoginException { - // TODO check permission? - return true; - } - - @Override - public boolean commit() throws LoginException { - subject.getPrincipals().add( - new AdminPrincipal(SecurityConstants.ADMIN_ID)); - return true; - } - - @Override - public boolean abort() throws LoginException { - return true; - } - - @Override - public boolean logout() throws LoginException { - subject.getPrincipals().removeAll( - subject.getPrincipals(AdminPrincipal.class)); - return true; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java deleted file mode 100644 index 8c267e314..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.argeo.jackrabbit; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.commons.io.IOUtils; -import org.apache.jackrabbit.commons.cnd.CndImporter; -import org.apache.jackrabbit.commons.cnd.ParseException; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.apache.jackrabbit.core.fs.FileSystemException; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrCallback; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -/** Migrate the data in a Jackrabbit repository. */ -@Deprecated -public class JackrabbitDataModelMigration implements Comparable { - private final static CmsLog log = CmsLog.getLog(JackrabbitDataModelMigration.class); - - private String dataModelNodePath; - private String targetVersion; - private URL migrationCnd; - private JcrCallback dataModification; - - /** - * Expects an already started repository with the old data model to migrate. - * Expects to be run with admin rights (Repository.login() will be used). - * - * @return true if a migration was performed and the repository needs to be - * restarted and its caches cleared. - */ - public Boolean migrate(Session session) { - long begin = System.currentTimeMillis(); - Reader reader = null; - try { - // check if already migrated - if (!session.itemExists(dataModelNodePath)) { -// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate."); - return false; - } -// Node dataModelNode = session.getNode(dataModelNodePath); -// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) { -// String currentVersion = dataModelNode.getProperty( -// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString(); -// if (compareVersions(currentVersion, targetVersion) >= 0) { -// log.info("Data model at version " + currentVersion -// + ", no need to migrate."); -// return false; -// } -// } - - // apply transitional CND - if (migrationCnd != null) { - reader = new InputStreamReader(migrationCnd.openStream()); - CndImporter.registerNodeTypes(reader, session, true); - session.save(); -// log.info("Registered migration node types from " + migrationCnd); - } - - // modify data - dataModification.execute(session); - - // apply changes - session.save(); - - long duration = System.currentTimeMillis() - begin; -// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in " -// + duration + "ms"); - return true; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", - e); - } catch (ParseException | IOException e) { - JcrUtils.discardQuietly(session); - throw new RuntimeException( - "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e); - } finally { - JcrUtils.logoutQuietly(session); - IOUtils.closeQuietly(reader); - } - } - - protected static int compareVersions(String version1, String version2) { - // TODO do a proper version analysis and comparison - return version1.compareTo(version2); - } - - /** To be called on a stopped repository. */ - public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) { - try { - String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml"; - // FIXME causes weird error in Eclipse - repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath); - if (log.isDebugEnabled()) - log.debug("Cleared " + customeNodeTypesPath); - } catch (RuntimeException e) { - throw e; - } catch (RepositoryException e) { - throw new JcrException(e); - } catch (FileSystemException e) { - throw new RuntimeException("Cannot clear node types cache.",e); - } - - // File customNodeTypes = new File(home.getPath() - // + "/repository/nodetypes/custom_nodetypes.xml"); - // if (customNodeTypes.exists()) { - // customNodeTypes.delete(); - // if (log.isDebugEnabled()) - // log.debug("Cleared " + customNodeTypes); - // } else { - // log.warn("File " + customNodeTypes + " not found."); - // } - } - - /* - * FOR USE IN (SORTED) SETS - */ - - public int compareTo(JackrabbitDataModelMigration dataModelMigration) { - // TODO make ordering smarter - if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath)) - return compareVersions(targetVersion, dataModelMigration.targetVersion); - else - return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof JackrabbitDataModelMigration)) - return false; - JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj; - return dataModelNodePath.equals(dataModelMigration.dataModelNodePath) - && targetVersion.equals(dataModelMigration.targetVersion); - } - - @Override - public int hashCode() { - return targetVersion.hashCode(); - } - - public void setDataModelNodePath(String dataModelNodePath) { - this.dataModelNodePath = dataModelNodePath; - } - - public void setTargetVersion(String targetVersion) { - this.targetVersion = targetVersion; - } - - public void setMigrationCnd(URL migrationCnd) { - this.migrationCnd = migrationCnd; - } - - public void setDataModification(JcrCallback dataModification) { - this.dataModification = dataModification; - } - - public String getDataModelNodePath() { - return dataModelNodePath; - } - - public String getTargetVersion() { - return targetVersion; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java deleted file mode 100644 index 77ad527e1..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory; -import org.apache.jackrabbit.jcr2spi.RepositoryImpl; -import org.apache.jackrabbit.spi.RepositoryServiceFactory; - -/** A customised {@link RepositoryFactory} access a remote DAVEX service. */ -public class ClientDavexRepositoryFactory implements RepositoryFactory { - public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI; - public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT; - - @SuppressWarnings("rawtypes") - @Override - public Repository getRepository(Map parameters) throws RepositoryException { - RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory(); - return RepositoryImpl - .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters)); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java deleted file mode 100644 index 7d86af217..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.argeo.jackrabbit.client; - -import javax.jcr.RepositoryException; - -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.protocol.HttpContext; -import org.apache.jackrabbit.spi.SessionInfo; -import org.apache.jackrabbit.spi2davex.BatchReadConfig; -import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl; - -/** - * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying - * {@link HttpClientContext}. - */ -public class ClientDavexRepositoryService extends RepositoryServiceImpl { - - public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig) - throws RepositoryException { - super(jcrServerURI, batchReadConfig); - } - - - -// public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, -// BatchReadConfig batchReadConfig, int itemInfoCacheSize, ConnectionOptions connectionOptions) -// throws RepositoryException { -// super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, connectionOptions); -// // TODO Auto-generated constructor stub -// } - - - -// public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, -// BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections) -// throws RepositoryException { -// super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections); -// } -// -// public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, -// BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException { -// super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize); -// } - - @Override - protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { - HttpClientContext result = HttpClientContext.create(); - result.setAuthCache(new NonSerialBasicAuthCache()); - return result; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java deleted file mode 100644 index 2af0835ea..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.util.Map; - -import javax.jcr.RepositoryException; - -import org.apache.jackrabbit.spi.RepositoryService; -import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl; -import org.apache.jackrabbit.spi2davex.BatchReadConfig; -import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; - -/** - * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a - * {@link ClientDavexRepositoryService}. - */ -public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory { - @Override - public RepositoryService createRepositoryService(Map parameters) throws RepositoryException { - // retrieve the repository uri - String uri; - if (parameters == null) { - uri = System.getProperty(PARAM_REPOSITORY_URI); - } else { - Object repoUri = parameters.get(PARAM_REPOSITORY_URI); - uri = (repoUri == null) ? null : repoUri.toString(); - } - if (uri == null) { - uri = DEFAULT_REPOSITORY_URI; - } - - // load other optional configuration parameters - BatchReadConfig brc = null; - int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE; - int maximumHttpConnections = 0; - - // since JCR-4120 the default workspace name is no longer set to 'default' - // note: if running with JCR Server < 1.5 a default workspace name must - // therefore be configured - String workspaceNameDefault = null; - - if (parameters != null) { - // batchRead config - Object param = parameters.get(PARAM_BATCHREAD_CONFIG); - if (param != null && param instanceof BatchReadConfig) { - brc = (BatchReadConfig) param; - } - - // itemCache size config - param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE); - if (param != null) { - try { - itemInfoCacheSize = Integer.parseInt(param.toString()); - } catch (NumberFormatException e) { - // ignore, use default - } - } - - // max connections config - param = parameters.get(PARAM_MAX_CONNECTIONS); - if (param != null) { - try { - maximumHttpConnections = Integer.parseInt(param.toString()); - } catch (NumberFormatException e) { - // using default - } - } - - param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT); - if (param != null) { - workspaceNameDefault = param.toString(); - } - } - - // FIXME adapt to changes in Jackrabbit -// if (maximumHttpConnections > 0) { -// return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize, -// maximumHttpConnections); -// } else { -// return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize); -// } - return null; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java deleted file mode 100644 index 3a122f142..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.protocol.HttpContext; -import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; -import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory; -import org.apache.jackrabbit.jcr2spi.RepositoryImpl; -import org.apache.jackrabbit.spi.RepositoryService; -import org.apache.jackrabbit.spi.RepositoryServiceFactory; -import org.apache.jackrabbit.spi.SessionInfo; -import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl; -import org.apache.jackrabbit.spi2davex.BatchReadConfig; -import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl; -import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; -import org.argeo.jcr.JcrUtils; - -/** Minimal client to test JCR DAVEX connectivity. */ -public class JackrabbitClient { - final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; - final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri"; - final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; - - public static void main(String[] args) { - String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0]; - String workspace = args.length < 2 ? "home" : args[1]; - - Repository repository = null; - Session session = null; - - URI uri; - try { - uri = new URI(repoUri); - } catch (URISyntaxException e1) { - throw new IllegalArgumentException(e1); - } - - if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) { - - RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() { - @SuppressWarnings("rawtypes") - public Repository getRepository(Map parameters) throws RepositoryException { - RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() { - - @Override - public RepositoryService createRepositoryService(Map parameters) - throws RepositoryException { - Object uri = parameters.get(JACKRABBIT_DAVEX_URI); - Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE); - BatchReadConfig brc = null; - // FIXME adapt to change in Jackrabbit -// return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc, -// ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) { -// -// @Override -// protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { -// HttpClientContext result = HttpClientContext.create(); -// result.setAuthCache(new NonSerialBasicAuthCache()); -// return result; -// } -// -// }; - return null; - } - }; - return RepositoryImpl.create( - new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters)); - } - }; - Map params = new HashMap(); - params.put(JACKRABBIT_DAVEX_URI, repoUri.toString()); - // FIXME make it configurable - params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys"); - - try { - repository = repositoryFactory.getRepository(params); - if (repository != null) - session = repository.login(workspace); - else - throw new IllegalArgumentException("Repository " + repoUri + " not found"); - } catch (RepositoryException e) { - e.printStackTrace(); - } - - } else { - Path path = Paths.get(uri.getPath()); - } - - try { - Node rootNode = session.getRootNode(); - NodeIterator nit = rootNode.getNodes(); - while (nit.hasNext()) { - System.out.println(nit.nextNode().getPath()); - } - - Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir"); - System.out.println("Created folder " + newNode.getPath()); - Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes()); - System.out.println("Created file " + newFile.getPath()); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) { - System.out.println("Read " + reader.readLine()); - } catch (IOException e) { - e.printStackTrace(); - } - newNode.getParent().remove(); - System.out.println("Removed new nodes"); - } catch (RepositoryException e) { - e.printStackTrace(); - } - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java deleted file mode 100644 index 3fb0db9a0..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java +++ /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 cache; - - public NonSerialBasicAuthCache() { - cache = new ConcurrentHashMap(); - } - - @Override - public void put(HttpHost host, AuthScheme authScheme) { - cache.put(host, authScheme); - } - - @Override - public AuthScheme get(HttpHost host) { - return cache.get(host); - } - - @Override - public void remove(HttpHost host) { - cache.remove(host); - } - - @Override - public void clear() { - cache.clear(); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java deleted file mode 100644 index a2eb98302..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.jackrabbit.fs; - -import org.argeo.jcr.fs.JcrFileSystemProvider; - -public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider { - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java deleted file mode 100644 index 1cae6e493..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java +++ /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 fileSystems = new HashMap<>(); - - @Override - public String getScheme() { - return "davex"; - } - - @Override - public FileSystem newFileSystem(URI uri, Map 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 params = new HashMap(); - 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()); - 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 ds = Files.newDirectoryStream(path); - for (Path p : ds) { - System.out.println("- " + p); - } - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java deleted file mode 100644 index e3a70d084..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java +++ /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 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()); - } catch (IOException e) { - throw new JcrFsException("Could not autocreate file system", e); - } - return fileSystem.getPath(path); - } - - public Repository getRepository() { - return repository; - } - - public Session login() throws RepositoryException { - return getRepository().login(credentials); - } - - /** - * Called after the repository has been created and before the file system is - * created. - */ - protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException { - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml deleted file mode 100644 index f2541fb4e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java deleted file mode 100644 index c9ec2c3b9..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Java NIO file system implementation based on Jackrabbit. */ -package org.argeo.jackrabbit.fs; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java deleted file mode 100644 index 17497d62c..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Jackrabbit utilities. */ -package org.argeo.jackrabbit; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml deleted file mode 100644 index 05267621f..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml deleted file mode 100644 index 3d2470863..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml deleted file mode 100644 index ecee5bdad..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml deleted file mode 100644 index 07a0d0428..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml deleted file mode 100644 index 967782820..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java deleted file mode 100644 index f98cf9947..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.argeo.jackrabbit.security; - -import java.security.Principal; -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.Privilege; - -import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; -import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrUtils; - -/** Utilities around Jackrabbit security extensions. */ -public class JackrabbitSecurityUtils { - private final static CmsLog log = CmsLog.getLog(JackrabbitSecurityUtils.class); - - /** - * Convenience method for denying a single privilege to a principal (user or - * role), typically jcr:all - */ - public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege) - throws RepositoryException { - List privileges = new ArrayList(); - 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 privs) throws RepositoryException { - // make sure the session is in line with the persisted state - session.refresh(false); - JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager(); - JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path); - -// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) { -// Principal currentPrincipal = ace.getPrincipal(); -// if (currentPrincipal.getName().equals(principal.getName())) { -// Privilege[] currentPrivileges = ace.getPrivileges(); -// if (currentPrivileges.length != privs.size()) -// break accessControlEntries; -// for (int i = 0; i < currentPrivileges.length; i++) { -// Privilege currP = currentPrivileges[i]; -// Privilege p = privs.get(i); -// if (!currP.getName().equals(p.getName())) { -// break accessControlEntries; -// } -// } -// return false; -// } -// } - - Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); - acl.addEntry(principal, privileges, false); - acm.setPolicy(path, acl); - if (log.isDebugEnabled()) { - StringBuffer privBuf = new StringBuffer(); - for (Privilege priv : privs) - privBuf.append(priv.getName()); - log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" - + session.getWorkspace().getName() + "'"); - } - session.refresh(true); - session.save(); - return true; - } - - /** Singleton. */ - private JackrabbitSecurityUtils() { - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java deleted file mode 100644 index f3a282c4e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Jackrabbit security utilities. */ -package org.argeo.jackrabbit.security; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java deleted file mode 100644 index 0418810ed..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.jcr; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Binary; -import javax.jcr.Property; -import javax.jcr.RepositoryException; - -/** - * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use - * in try/catch blocks. - */ -public class Bin implements Binary, AutoCloseable { - private final Binary wrappedBinary; - - public Bin(Property property) throws RepositoryException { - this(property.getBinary()); - } - - public Bin(Binary wrappedBinary) { - if (wrappedBinary == null) - throw new IllegalArgumentException("Wrapped binary cannot be null"); - this.wrappedBinary = wrappedBinary; - } - - // private static Binary getBinary(Property property) throws IOException { - // try { - // return property.getBinary(); - // } catch (RepositoryException e) { - // throw new IOException("Cannot get binary from property " + property, e); - // } - // } - - @Override - public void close() { - dispose(); - } - - @Override - public InputStream getStream() throws RepositoryException { - return wrappedBinary.getStream(); - } - - @Override - public int read(byte[] b, long position) throws IOException, RepositoryException { - return wrappedBinary.read(b, position); - } - - @Override - public long getSize() throws RepositoryException { - return wrappedBinary.getSize(); - } - - @Override - public void dispose() { - wrappedBinary.dispose(); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java deleted file mode 100644 index b4124eea5..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java +++ /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 iterator; - private Integer position = 0; - - public CollectionNodeIterator(Collection nodes) { - super(); - this.collectionSize = (long) nodes.size(); - this.iterator = nodes.iterator(); - } - - public void skip(long skipNum) { - if (skipNum < 0) - throw new IllegalArgumentException( - "Skip count has to be positive: " + skipNum); - - for (long i = 0; i < skipNum; i++) { - if (!hasNext()) - throw new NoSuchElementException("Last element past (position=" - + getPosition() + ")"); - nextNode(); - } - } - - public long getSize() { - return collectionSize; - } - - public long getPosition() { - return position; - } - - public boolean hasNext() { - return iterator.hasNext(); - } - - public Object next() { - return nextNode(); - } - - public void remove() { - iterator.remove(); - } - - public Node nextNode() { - Node node = iterator.next(); - position++; - return node; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java deleted file mode 100644 index d873ef652..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.argeo.jcr; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -import javax.jcr.observation.EventListener; -import javax.jcr.observation.ObservationManager; - -import org.argeo.api.cms.CmsLog; - -/** To be overridden */ -public class DefaultJcrListener implements EventListener { - private final static CmsLog log = CmsLog.getLog(DefaultJcrListener.class); - private Session session; - private String path = "/"; - private Boolean deep = true; - - public void start() { - try { - addEventListener(session().getWorkspace().getObservationManager()); - if (log.isDebugEnabled()) - log.debug("Registered JCR event listener on " + path); - } catch (RepositoryException e) { - throw new JcrException("Cannot register event listener", e); - } - } - - public void stop() { - try { - session().getWorkspace().getObservationManager() - .removeEventListener(this); - if (log.isDebugEnabled()) - log.debug("Unregistered JCR event listener on " + path); - } catch (RepositoryException e) { - throw new JcrException("Cannot unregister event listener", e); - } - } - - /** Default is listen to all events */ - protected Integer getEvents() { - return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED - | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; - } - - /** To be overidden */ - public void onEvent(EventIterator events) { - while (events.hasNext()) { - Event event = events.nextEvent(); - log.debug(event); - } - } - - /** To be overidden */ - protected void addEventListener(ObservationManager observationManager) - throws RepositoryException { - observationManager.addEventListener(this, getEvents(), path, deep, - null, null, false); - } - - private Session session() { - return session; - } - - public void setPath(String path) { - this.path = path; - } - - public void setDeep(Boolean deep) { - this.deep = deep; - } - - public void setSession(Session session) { - this.session = session; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java deleted file mode 100644 index 1936f2398..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java +++ /dev/null @@ -1,994 +0,0 @@ -package org.argeo.jcr; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.text.MessageFormat; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.List; - -import javax.jcr.Binary; -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.Workspace; -import javax.jcr.nodetype.NodeType; -import javax.jcr.query.Query; -import javax.jcr.query.QueryManager; -import javax.jcr.query.Row; -import javax.jcr.security.Privilege; -import javax.jcr.version.Version; -import javax.jcr.version.VersionHistory; -import javax.jcr.version.VersionIterator; -import javax.jcr.version.VersionManager; - -import org.apache.commons.io.IOUtils; - -/** - * Utility class whose purpose is to make using JCR less verbose by - * systematically using unchecked exceptions and returning null - * 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 Files 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"; - /** - * jcr:name, 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"; - /** - * jcr:path, 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"; - /** - * jcr:primaryType with prefix instead of namespace (as in - * {@link Property#JCR_PRIMARY_TYPE}. - */ - public final static String JCR_PRIMARY_TYPE = "jcr:primaryType"; - /** - * jcr:mixinTypes with prefix instead of namespace (as in - * {@link Property#JCR_MIXIN_TYPES}. - */ - public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; - /** - * jcr:uuid with prefix instead of namespace (as in - * {@link Property#JCR_UUID}. - */ - public final static String JCR_UUID = "jcr:uuid"; - /** - * jcr:created with prefix instead of namespace (as in - * {@link Property#JCR_CREATED}. - */ - public final static String JCR_CREATED = "jcr:created"; - /** - * jcr:createdBy with prefix instead of namespace (as in - * {@link Property#JCR_CREATED_BY}. - */ - public final static String JCR_CREATED_BY = "jcr:createdBy"; - /** - * jcr:lastModified with prefix instead of namespace (as in - * {@link Property#JCR_LAST_MODIFIED}. - */ - public final static String JCR_LAST_MODIFIED = "jcr:lastModified"; - /** - * jcr:lastModifiedBy 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 iterate(NodeIterator nodeIterator) { - return new Iterable() { - - @Override - public Iterator 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 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 getNodes(Node node) { - List 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 null 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 null 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 null, the primary - * type will be used (typically for XML structures) - * @param primaryType the primary type, if null - * {@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 null - * @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 null 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 - * null. - * - * @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 - * null 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 - * defaultValue 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 null 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 - * defaultValue 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 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 getAs(Node node, String property, Class 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 List 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 List getMultiple(Property p) { - try { - List 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 getLinearVersions(VersionHistory versionHistory) { - try { - List 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 null 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 null if not found. - */ - public static Node getNode(Session session, String sql, Object... args) { - QueryManager queryManager; - try { - queryManager = session.getWorkspace().getQueryManager(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get query manager from session " + session, e); - } - return getNode(queryManager, sql, args); - } - - public static Node getRowNode(Row row, String selectorName) { - try { - return row.getNode(selectorName); - } catch (RepositoryException e) { - throw new JcrException("Cannot get node " + selectorName + " from row", e); - } - } - - /** Singleton. */ - private Jcr() { - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java deleted file mode 100644 index 351929f8d..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java +++ /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
- * value := group1,group2,user1 - */ - private Map principalPrivileges = new HashMap(); - - 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 privs = new ArrayList(); - 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 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 groupPrivileges) { - this.principalPrivileges = groupPrivileges; - } - - public void setPrincipalPrivileges(Map principalPrivileges) { - this.principalPrivileges = principalPrivileges; - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - public void setSecurityWorkspace(String securityWorkspace) { - this.securityWorkspace = securityWorkspace; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java deleted file mode 100644 index efbaabe82..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java +++ /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 { - /** @deprecated Use {@link #apply(Session)} instead. */ - @Deprecated - public default Object execute(Session session) { - return apply(session); - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java deleted file mode 100644 index c77874376..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.jcr; - -import javax.jcr.RepositoryException; - -/** - * Wraps a {@link RepositoryException} in a {@link RuntimeException}. - */ -public class JcrException extends IllegalStateException { - private static final long serialVersionUID = -4530350094877964989L; - - public JcrException(String message, RepositoryException e) { - super(message, e); - } - - public JcrException(RepositoryException e) { - super(e); - } - - public RepositoryException getRepositoryCause() { - return (RepositoryException) getCause(); - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java deleted file mode 100644 index 71cf961e0..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java +++ /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 UNKNOWN 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 true if cancellation has been requested, and - * false otherwise - * @see #setCanceled(boolean) - */ - public boolean isCanceled(); - - /** - * Sets the cancel state to the given value. - * - * @param value - * true indicates that cancelation has been - * requested (but not necessarily acknowledged); - * false clears this flag - * @see #isCanceled() - */ - public void setCanceled(boolean value); - - /** - * Sets the task name to the given value. This method is used to restore the - * task label after a nested operation was executed. Normally there is no - * need for clients to call this method. - * - * @param name - * the name (or description) of the main task - * @see #beginTask(java.lang.String, int) - */ - public void setTaskName(String name); - - /** - * Notifies that a subtask of the main task is beginning. Subtasks are - * optional; the main task might not have subtasks. - * - * @param name - * the name (or description) of the subtask - */ - public void subTask(String name); - - /** - * Notifies that a given number of work unit of the main task has been - * completed. Note that this amount represents an installment, as opposed to - * a cumulative amount of work done to date. - * - * @param work - * a non-negative number of work units just completed - */ - public void worked(int work); - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java deleted file mode 100644 index 3228eee74..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java +++ /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 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 keys = Arrays.asList(getRepository().getDescriptorKeys()); - keys.addAll(additionalDescriptors.keySet()); - return keys.toArray(new String[keys.size()]); - } - - /** Central login method */ - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, RepositoryException { - Session session; - try { - session = getRepository(workspaceName).login(credentials, workspaceName); - } catch (NoSuchWorkspaceException e) { - if (autocreateWorkspaces && workspaceName != null) - session = createWorkspaceAndLogsIn(credentials, workspaceName); - else - throw e; - } - processNewSession(session, workspaceName); - return session; - } - - public Session login() throws LoginException, RepositoryException { - return login(null, null); - } - - public Session login(Credentials credentials) throws LoginException, RepositoryException { - return login(credentials, null); - } - - public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException { - return login(null, workspaceName); - } - - /** Called after a session has been created, does nothing by default. */ - protected void processNewSession(Session session, String workspaceName) { - } - - /** - * Wraps access to the repository, making sure it is available. - * - * @deprecated Use {@link #getDefaultRepository()} instead. - */ - @Deprecated - protected synchronized Repository getRepository() { - return getDefaultRepository(); - } - - protected synchronized Repository getDefaultRepository() { - return repository; - } - - protected synchronized Repository getRepository(String workspaceName) { - return getDefaultRepository(); - } - - /** - * Logs in to the default workspace, creates the required workspace, logs out, - * logs in to the required workspace. - */ - protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName) - throws RepositoryException { - if (workspaceName == null) - throw new IllegalArgumentException("No workspace specified."); - Session session = getRepository(workspaceName).login(credentials); - session.getWorkspace().createWorkspace(workspaceName); - session.logout(); - return getRepository(workspaceName).login(credentials, workspaceName); - } - - public boolean isStandardDescriptor(String key) { - return getRepository().isStandardDescriptor(key); - } - - public boolean isSingleValueDescriptor(String key) { - if (additionalDescriptors.containsKey(key)) - return true; - return getRepository().isSingleValueDescriptor(key); - } - - public Value getDescriptorValue(String key) { - if (additionalDescriptors.containsKey(key)) - return new StrValue(additionalDescriptors.get(key)); - return getRepository().getDescriptorValue(key); - } - - public Value[] getDescriptorValues(String key) { - return getRepository().getDescriptorValues(key); - } - - public synchronized void setRepository(Repository repository) { - this.repository = repository; - } - - public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) { - this.autocreateWorkspaces = autocreateWorkspaces; - } - - protected static class StrValue implements Value { - private final String str; - - public StrValue(String str) { - this.str = str; - } - - @Override - public String getString() throws ValueFormatException, IllegalStateException, RepositoryException { - return str; - } - - @Override - public InputStream getStream() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override - public Binary getBinary() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override - public long getLong() throws ValueFormatException, RepositoryException { - try { - return Long.parseLong(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public double getDouble() throws ValueFormatException, RepositoryException { - try { - return Double.parseDouble(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { - try { - return new BigDecimal(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public Calendar getDate() throws ValueFormatException, RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getBoolean() throws ValueFormatException, RepositoryException { - try { - return Boolean.parseBoolean(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public int getType() { - return PropertyType.STRING; - } - - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java deleted file mode 100644 index 82a65e7f1..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.jcr; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */ -public class JcrUrlStreamHandler extends URLStreamHandler { - private final Session session; - - public JcrUrlStreamHandler(Session session) { - this.session = session; - } - - @Override - protected URLConnection openConnection(final URL u) throws IOException { - // TODO Auto-generated method stub - return new URLConnection(u) { - - @Override - public void connect() throws IOException { - String itemPath = u.getPath(); - try { - if (!session.itemExists(itemPath)) - throw new IOException("No item under " + itemPath); - - Item item = session.getItem(u.getPath()); - if (item.isNode()) { - // this should be a nt:file node - Node node = (Node) item; - if (!node.getPrimaryNodeType().isNodeType( - NodeType.NT_FILE)) - throw new IOException("Node " + node + " is not a " - + NodeType.NT_FILE); - - } else { - Property property = (Property) item; - if(property.getType()==PropertyType.BINARY){ - //Binary binary = property.getBinary(); - - } - } - } catch (RepositoryException e) { - IOException ioe = new IOException( - "Unexpected JCR exception"); - ioe.initCause(e); - throw ioe; - } - } - - @Override - public InputStream getInputStream() throws IOException { - // TODO Auto-generated method stub - return super.getInputStream(); - } - - }; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java deleted file mode 100644 index 3be8be184..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java +++ /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 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 nodeIteratorToList(NodeIterator nodeIterator) { - List nodes = new ArrayList(); - 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 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 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 tokenize(String path) { - List tokens = new ArrayList(); - 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 diffProperties(Node reference, Node observed) { - Map diffs = new TreeMap(); - 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 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 diffProperties(Node reference, Node observed, List properties) { - Map diffs = new TreeMap(); - try { - Iterator 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 false, 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, - * these properties are - * not automatically updated, 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 - * Node type - * Notation 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 privileges = new ArrayList(); - 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 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("'); - } else { - sb.append("/>"); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java deleted file mode 100644 index 666b2593e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java +++ /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 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 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 getChecksums(Node node) { - try { - List res = new ArrayList<>(); - if (!node.hasProperty(JcrxName.JCRX_SUM)) - return res; - Property property = node.getProperty(JcrxName.JCRX_SUM); - for (Value value : property.getValues()) { - res.add(value.getString()); - } - return res; - } catch (RepositoryException e) { - throw new JcrException("Cannot get checksums from " + node, e); - } - } - -// /** Replace all checksums with this single one. */ -// public static void setChecksum(Node node, String checksum) { -// setChecksums(node, Collections.singletonList(checksum)); -// } - - /** Retrieves the checksum with this algorithm, or null if not found. */ - public static String getChecksum(Node node, String algorithm) { - int stringLength; - switch (algorithm) { - case MD5: - stringLength = LENGTH_MD5; - break; - case SHA1: - stringLength = LENGTH_SHA1; - break; - case SHA256: - stringLength = LENGTH_SHA256; - break; - case SHA512: - stringLength = LENGTH_SHA512; - break; - default: - throw new IllegalArgumentException("Unkown algorithm " + algorithm); - } - return getChecksum(node, stringLength); - } - - /** Retrieves the checksum with this string length, or null if not found. */ - public static String getChecksum(Node node, int stringLength) { - try { - if (!node.hasProperty(JcrxName.JCRX_SUM)) - return null; - Property property = node.getProperty(JcrxName.JCRX_SUM); - for (Value value : property.getValues()) { - String str = value.getString(); - if (str.length() == stringLength) - return str; - } - return null; - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get checksum for " + node, e); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java deleted file mode 100644 index 9dd43adce..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.jcr; - -/** Names declared by the JCR extensions. */ -public interface JcrxName { - /** The multiple property holding various coherent checksums. */ - public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum"; -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java deleted file mode 100644 index 0cbad3341..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.argeo.jcr; - -/** Node types declared by the JCR extensions. */ -public interface JcrxType { - /** - * Node type for an XML value, which will be serialized in XML as an element - * containing text. - */ - public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue"; - - /** Node type for the node containing the text. */ - public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext"; - - /** Mixin node type for a set of checksums. */ - public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum"; - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java deleted file mode 100644 index 71e76fe9b..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.argeo.jcr; - -import javax.jcr.Value; - -/** The result of the comparison of two JCR properties. */ -public class PropertyDiff { - public final static Integer MODIFIED = 0; - public final static Integer ADDED = 1; - public final static Integer REMOVED = 2; - - private final Integer type; - private final String relPath; - private final Value referenceValue; - private final Value newValue; - - public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) { - super(); - - if (type == MODIFIED) { - if (referenceValue == null || newValue == null) - throw new IllegalArgumentException("Reference and new values must be specified."); - } else if (type == ADDED) { - if (referenceValue != null || newValue == null) - throw new IllegalArgumentException("New value and only it must be specified."); - } else if (type == REMOVED) { - if (referenceValue == null || newValue != null) - throw new IllegalArgumentException("Reference value and only it must be specified."); - } else { - throw new IllegalArgumentException("Unkown diff type " + type); - } - - if (relPath == null) - throw new IllegalArgumentException("Relative path must be specified"); - - this.type = type; - this.relPath = relPath; - this.referenceValue = referenceValue; - this.newValue = newValue; - } - - public Integer getType() { - return type; - } - - public String getRelPath() { - return relPath; - } - - public Value getReferenceValue() { - return referenceValue; - } - - public Value getNewValue() { - return newValue; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java deleted file mode 100644 index 4f42f2d9c..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.argeo.jcr; - -import java.security.Principal; - -/** Canonical implementation of a {@link Principal} */ -class SimplePrincipal implements Principal { - private final String name; - - public SimplePrincipal(String name) { - if (name == null) - throw new IllegalArgumentException("Principal name cannot be null"); - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj instanceof Principal) - return name.equals((((Principal) obj).getName())); - return name.equals(obj.toString()); - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return new SimplePrincipal(name); - } - - @Override - public String toString() { - return name; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java deleted file mode 100644 index 2208627ab..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java +++ /dev/null @@ -1,279 +0,0 @@ -package org.argeo.jcr; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.jcr.LoginException; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.argeo.api.cms.CmsLog; - -/** Proxy JCR sessions and attach them to calling threads. */ -@Deprecated -public abstract class ThreadBoundJcrSessionFactory { - private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class); - - private Repository repository; - /** can be injected as list, only used if repository is null */ - private List repositories; - - private ThreadLocal session = new ThreadLocal(); - 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 threads = Collections.synchronizedList(new ArrayList()); - private final Map activeSessions = Collections.synchronizedMap(new HashMap()); - 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 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 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 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 repositories) { - this.repositories = repositories; - } - - public void setDefaultUsername(String defaultUsername) { - this.defaultUsername = defaultUsername; - } - - public void setDefaultPassword(String defaultPassword) { - this.defaultPassword = defaultPassword; - } - - public void setForceDefaultCredentials(Boolean forceDefaultCredentials) { - this.forceDefaultCredentials = forceDefaultCredentials; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - protected class JcrSessionInvocationHandler implements InvocationHandler { - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException { - Session threadSession = session.get(); - if (threadSession == null) { - if ("logout".equals(method.getName()))// no need to login - return Void.TYPE; - else if ("toString".equals(method.getName()))// maybe logging - return "Uninitialized Argeo thread bound JCR session"; - threadSession = login(); - } - - preCall(threadSession); - Object ret; - try { - ret = method.invoke(threadSession, args); - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - if (cause instanceof RepositoryException) - throw (RepositoryException) cause; - else - throw cause; - } - if ("logout".equals(method.getName())) { - session.remove(); - Thread thread = Thread.currentThread(); - removeSession(thread); - if (log.isTraceEnabled()) - log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread " - + thread.getId()); - } - return ret; - } - } - - /** Monitors registered thread in order to clean up dead ones. */ - private class MonitoringThread extends Thread { - - public MonitoringThread() { - super("ThreadBound JCR Session Monitor"); - } - - @Override - public void run() { - while (isActive()) { - cleanDeadThreads(); - } - } - - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java deleted file mode 100644 index dab55548b..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java +++ /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 diffs; - private Calendar updateTime; - - public VersionDiff(String userId, Calendar updateTime, - Map diffs) { - this.userId = userId; - this.updateTime = updateTime; - this.diffs = diffs; - } - - public String getUserId() { - return userId; - } - - public Map getDiffs() { - return diffs; - } - - public Calendar getUpdateTime() { - return updateTime; - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java deleted file mode 100644 index d6550feee..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.jcr.JcrUtils; - -/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */ -public class BinaryChannel implements SeekableByteChannel { - private final Node file; - private Binary binary; - private boolean open = true; - - private long position = 0; - - private FileChannel fc = null; - - public BinaryChannel(Node file, Path path) throws RepositoryException, IOException { - this.file = file; - Session session = file.getSession(); - synchronized (session) { - if (file.isNodeType(NodeType.NT_FILE)) { - if (file.hasNode(Node.JCR_CONTENT)) { - Node data = file.getNode(Property.JCR_CONTENT); - this.binary = data.getProperty(Property.JCR_DATA).getBinary(); - } else { - Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); - data.addMixin(NodeType.MIX_LAST_MODIFIED); - try (InputStream in = new ByteArrayInputStream(new byte[0])) { - this.binary = data.getSession().getValueFactory().createBinary(in); - } - data.setProperty(Property.JCR_DATA, this.binary); - - // MIME type - String mime = Files.probeContentType(path); - // String mime = fileTypeMap.getContentType(file.getName()); - data.setProperty(Property.JCR_MIMETYPE, mime); - - session.refresh(true); - session.save(); - session.notifyAll(); - } - } else { - throw new IllegalArgumentException( - "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")"); - } - } - } - - @Override - public synchronized boolean isOpen() { - return open; - } - - @Override - public synchronized void close() throws IOException { - if (isModified()) { - Binary newBinary = null; - try { - Session session = file.getSession(); - synchronized (session) { - fc.position(0); - InputStream in = Channels.newInputStream(fc); - newBinary = session.getValueFactory().createBinary(in); - file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary); - session.refresh(true); - session.save(); - open = false; - session.notifyAll(); - } - } catch (RepositoryException e) { - throw new IOException("Cannot close " + file, e); - } finally { - JcrUtils.closeQuietly(newBinary); - // IOUtils.closeQuietly(fc); - if (fc != null) { - fc.close(); - } - } - } else { - clearReadState(); - open = false; - } - } - - @Override - public int read(ByteBuffer dst) throws IOException { - if (isModified()) { - return fc.read(dst); - } else { - - try { - int read; - byte[] arr = dst.array(); - read = binary.read(arr, position); - - if (read != -1) - position = position + read; - return read; - } catch (RepositoryException e) { - throw new IOException("Cannot read into buffer", e); - } - } - } - - @Override - public int write(ByteBuffer src) throws IOException { - int written = getFileChannel().write(src); - return written; - } - - @Override - public long position() throws IOException { - if (isModified()) - return getFileChannel().position(); - else - return position; - } - - @Override - public SeekableByteChannel position(long newPosition) throws IOException { - if (isModified()) { - getFileChannel().position(position); - } else { - this.position = newPosition; - } - return this; - } - - @Override - public long size() throws IOException { - if (isModified()) { - return getFileChannel().size(); - } else { - try { - return binary.getSize(); - } catch (RepositoryException e) { - throw new IOException("Cannot get size", e); - } - } - } - - @Override - public SeekableByteChannel truncate(long size) throws IOException { - getFileChannel().truncate(size); - return this; - } - - private FileChannel getFileChannel() throws IOException { - try { - if (fc == null) { - Path tempPath = Files.createTempFile(getClass().getSimpleName(), null); - fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ, - StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE); - ReadableByteChannel readChannel = Channels.newChannel(binary.getStream()); - fc.transferFrom(readChannel, 0, binary.getSize()); - clearReadState(); - } - return fc; - } catch (RepositoryException e) { - throw new IOException("Cannot get temp file channel", e); - } - } - - private boolean isModified() { - return fc != null; - } - - private void clearReadState() { - position = -1; - JcrUtils.closeQuietly(binary); - binary = null; - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java deleted file mode 100644 index 7c9711bf0..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.argeo.jcr.fs; - -import static javax.jcr.Property.JCR_CREATED; -import static javax.jcr.Property.JCR_LAST_MODIFIED; - -import java.nio.file.attribute.FileTime; -import java.time.Instant; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.jcr.JcrUtils; - -public class JcrBasicfileAttributes implements NodeFileAttributes { - private final Node node; - - private final static FileTime EPOCH = FileTime.fromMillis(0); - - public JcrBasicfileAttributes(Node node) { - if (node == null) - throw new JcrFsException("Node underlying the attributes cannot be null"); - this.node = node; - } - - @Override - public FileTime lastModifiedTime() { - try { - if (node.hasProperty(JCR_LAST_MODIFIED)) { - Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant(); - return FileTime.from(instant); - } else if (node.hasProperty(JCR_CREATED)) { - Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); - return FileTime.from(instant); - } -// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { -// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant(); -// return FileTime.from(instant); -// } - return EPOCH; - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get last modified time", e); - } - } - - @Override - public FileTime lastAccessTime() { - return lastModifiedTime(); - } - - @Override - public FileTime creationTime() { - try { - if (node.hasProperty(JCR_CREATED)) { - Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); - return FileTime.from(instant); - } else if (node.hasProperty(JCR_LAST_MODIFIED)) { - Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant(); - return FileTime.from(instant); - } -// if (node.isNodeType(NodeType.MIX_CREATED)) { -// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); -// return FileTime.from(instant); -// } - return EPOCH; - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get creation time", e); - } - } - - @Override - public boolean isRegularFile() { - try { - return node.isNodeType(NodeType.NT_FILE); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check if regular file", e); - } - } - - @Override - public boolean isDirectory() { - try { - if (node.isNodeType(NodeType.NT_FOLDER)) - return true; - // all other non file nodes - return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE)); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check if directory", e); - } - } - - @Override - public boolean isSymbolicLink() { - try { - return node.isNodeType(NodeType.NT_LINKED_FILE); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check if linked file", e); - } - } - - @Override - public boolean isOther() { - return !(isDirectory() || isRegularFile() || isSymbolicLink()); - } - - @Override - public long size() { - if (isRegularFile()) { - Binary binary = null; - try { - binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary(); - return binary.getSize(); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check size", e); - } finally { - JcrUtils.closeQuietly(binary); - } - } - return -1; - } - - @Override - public Object fileKey() { - try { - return node.getIdentifier(); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get identifier", e); - } - } - - @Override - public Node getNode() { - return node; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java deleted file mode 100644 index 4b329810f..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java +++ /dev/null @@ -1,252 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.WatchService; -import java.nio.file.attribute.UserPrincipalLookupService; -import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -import javax.jcr.Credentials; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.acr.fs.AbstractFsStore; -import org.argeo.api.acr.fs.AbstractFsSystem; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrUtils; - -public class JcrFileSystem extends AbstractFsSystem { - private final JcrFileSystemProvider provider; - - private final Repository repository; - private Session session; - private WorkspaceFileStore baseFileStore; - - private Map 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 listDirectMounts(Path base) { - String baseStr = base.toString(); - Set 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 getRootDirectories() { - Set single = new HashSet<>(); - single.add(new JcrPath(this, JcrPath.separator)); - return single; - } - - @Override - public Iterable getFileStores() { - List stores = new ArrayList<>(); - stores.add(baseFileStore); - stores.addAll(mounts.values()); - return stores; - } - - @Override - public Set supportedFileAttributeViews() { - try { - String[] prefixes = session.getNamespacePrefixes(); - Set res = new HashSet<>(); - for (String prefix : prefixes) - res.add(prefix); - res.add("basic"); - return res; - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get supported file attributes views", e); - } - } - - @Override - public Path getPath(String first, String... more) { - StringBuilder sb = new StringBuilder(first); - // TODO Make it more robust - for (String part : more) - sb.append('/').append(part); - return new JcrPath(this, sb.toString()); - } - - @Override - public PathMatcher getPathMatcher(String syntaxAndPattern) { - throw new UnsupportedOperationException(); - } - - @Override - public UserPrincipalLookupService getUserPrincipalLookupService() { - throw new UnsupportedOperationException(); - } - - @Override - public WatchService newWatchService() throws IOException { - throw new UnsupportedOperationException(); - } - -// public Session getSession() { -// return session; -// } - - public Repository getRepository() { - return repository; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java deleted file mode 100644 index 74d9a198e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java +++ /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 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 newDirectoryStream(Path dir, Filter 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 getFileAttributeView(Path path, Class type, LinkOption... options) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("unchecked") - @Override - public A readAttributes(Path path, Class 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 readAttributes(Path path, String attributes, LinkOption... options) throws IOException { - try { - Node node = toNode(path); - String pattern = attributes.replace(',', '|'); - Map res = new HashMap(); - PropertyIterator it = node.getProperties(pattern); - props: while (it.hasNext()) { - Property prop = it.nextProperty(); - PropertyDefinition pd = prop.getDefinition(); - if (pd.isMultiple()) - continue props; - int requiredType = pd.getRequiredType(); - switch (requiredType) { - case PropertyType.LONG: - res.put(prop.getName(), prop.getLong()); - break; - case PropertyType.DOUBLE: - res.put(prop.getName(), prop.getDouble()); - break; - case PropertyType.BOOLEAN: - res.put(prop.getName(), prop.getBoolean()); - break; - case PropertyType.DATE: - res.put(prop.getName(), prop.getDate()); - break; - case PropertyType.BINARY: - byte[] arr = JcrUtils.getBinaryAsBytes(prop); - res.put(prop.getName(), arr); - break; - default: - res.put(prop.getName(), prop.getString()); - } - } - return res; - } catch (RepositoryException e) { - throw new IOException("Cannot read attributes of " + path, e); - } - } - - @Override - public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { - Node node = toNode(path); - try { - Session session = node.getSession(); - synchronized (session) { - if (value instanceof byte[]) { - JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value); - } else if (value instanceof Calendar) { - node.setProperty(attribute, (Calendar) value); - } else { - node.setProperty(attribute, value.toString()); - } - save(session); - } - } catch (RepositoryException e) { - discardChanges(node); - throw new IOException("Cannot set attribute " + attribute + " on " + path, e); - } - } - - protected Node toNode(Path path) { - try { - return ((JcrPath) path).getNode(); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e); - } - } - - /** Discard changes in the underlying session */ - protected void discardChanges(Node node) { - if (node == null) - return; - try { - // discard changes - node.getSession().refresh(false); - } catch (RepositoryException e) { - e.printStackTrace(); - // TODO log out session? - // TODO use Commons logging? - } - } - - /** Make sure save is robust. */ - protected void save(Session session) throws RepositoryException { - session.refresh(true); - session.save(); - session.notifyAll(); - } - - /** - * To be overriden in order to support the ~ path, with an implementation - * specific concept of user home. - * - * @return null by default - */ - public Node getUserHome(Repository session) { - return null; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java deleted file mode 100644 index f214fdc44..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.jcr.fs; - - -/** Exception related to the JCR FS */ -public class JcrFsException extends RuntimeException { - private static final long serialVersionUID = -7973896038244922980L; - - public JcrFsException(String message, Throwable e) { - super(message, e); - } - - public JcrFsException(String message) { - super(message); - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java deleted file mode 100644 index 7318b7096..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java +++ /dev/null @@ -1,384 +0,0 @@ -package org.argeo.jcr.fs; - -import java.nio.file.Path; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.acr.fs.AbstractFsPath; - -/** A {@link Path} which contains a reference to a JCR {@link Node}. */ -public class JcrPath extends AbstractFsPath { - 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 iterator() { -// return new Iterator() { -// private int i = 0; -// -// @Override -// public boolean hasNext() { -// return (i < getNameCount()); -// } -// -// @Override -// public Path next() { -// if (i < getNameCount()) { -// Path result = getName(i); -// i++; -// return result; -// } else { -// throw new NoSuchElementException(); -// } -// } -// -// @Override -// public void remove() { -// throw new UnsupportedOperationException(); -// } -// }; -// } -// -// @Override -// public Path relativize(Path other) { -// if (equals(other)) -// return new JcrPath(fs, ""); -// if (other.startsWith(this)) { -// String p1 = toString(); -// String p2 = other.toString(); -// String relative = p2.substring(p1.length(), p2.length()); -// if (relative.charAt(0) == '/') -// relative = relative.substring(1); -// return new JcrPath(fs, relative); -// } -// throw new IllegalArgumentException(other + " cannot be relativized against " + this); -// } - -// @Override -// public URI toUri() { -// try { -// return new URI(fs.provider().getScheme(), toString(), null); -// } catch (URISyntaxException e) { -// throw new JcrFsException("Cannot create URI for " + toString(), e); -// } -// } -// -// @Override -// public Path toAbsolutePath() { -// if (isAbsolute()) -// return this; -// return new JcrPath(fs, fileStore, path, true); -// } -// -// @Override -// public Path toRealPath(LinkOption... options) throws IOException { -// return this; -// } -// -// @Override -// public File toFile() { -// throw new UnsupportedOperationException(); -// } - - public Node getNode() throws RepositoryException { - if (!isAbsolute())// TODO default dir - throw new JcrFsException("Cannot get a JCR node from a relative path"); - assert getFileStore() != null; - return getFileStore().toNode(getSegments()); -// String pathStr = toJcrPath(); -// Session session = fs.getSession(); -// // TODO synchronize on the session ? -// if (!session.itemExists(pathStr)) -// return null; -// return session.getNode(pathStr); - } -// -// @Override -// public boolean equals(Object obj) { -// if (!(obj instanceof JcrPath)) -// return false; -// JcrPath other = (JcrPath) obj; -// -// if (path == null) {// root -// if (other.path == null)// root -// return true; -// else -// return false; -// } else { -// if (other.path == null)// root -// return false; -// } -// // non root -// if (path.length != other.path.length) -// return false; -// for (int i = 0; i < path.length; i++) { -// if (!path[i].equals(other.path[i])) -// return false; -// } -// return true; -// } - -// @Override -// public int hashCode() { -// return hashCode; -// } - -// @Override -// protected Object clone() throws CloneNotSupportedException { -// return new JcrPath(fs, toString()); -// } - -// @Override -// protected void finalize() throws Throwable { -// Arrays.fill(path, null); -// } - - - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java deleted file mode 100644 index eda07a548..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java +++ /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 { - private final JcrFileSystem fs; - private final NodeIterator nodeIterator; - private final Iterator additionalPaths; - private final Filter filter; - - public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator additionalPaths, - Filter filter) { - this.fs = fs; - this.nodeIterator = nodeIterator; - this.additionalPaths = additionalPaths; - this.filter = filter; - } - - @Override - public void close() throws IOException { - } - - @Override - public Iterator iterator() { - return new Iterator() { - private JcrPath next = null; - - @Override - public synchronized boolean hasNext() { - if (next != null) - return true; - nodes: while (nodeIterator.hasNext()) { - try { - Node node = nodeIterator.nextNode(); - String nodeName = node.getName(); - if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:")) - continue nodes; - if (fs.skipNode(node)) - continue nodes; - next = new JcrPath(fs, node); - if (filter != null) { - if (filter.accept(next)) - break nodes; - } else - break nodes; - } catch (Exception e) { - throw new JcrFsException("Could not get next path", e); - } - } - - if (next == null) { - if (additionalPaths.hasNext()) - next = additionalPaths.next(); - } - - return next != null; - } - - @Override - public synchronized Path next() { - if (!hasNext())// make sure has next has been called - return null; - JcrPath res = next; - next = null; - return res; - } - - }; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java deleted file mode 100644 index 8054d52f8..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.jcr.fs; - -import java.nio.file.attribute.BasicFileAttributes; - -import javax.jcr.Node; - -public interface NodeFileAttributes extends BasicFileAttributes { - public Node getNode(); -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java deleted file mode 100644 index 4643c8c9c..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java +++ /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; - -/** - * Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons - * 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 true, 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 strings = new ArrayList(); - 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 oldString in text - * with newString. - * - * @param text - * @param oldString - * old substring to be replaced with newString - * @param newString - * new substring to replace occurrences of oldString - * @return a string - */ - public static String replace(String text, String oldString, String newString) { - if (text == null || oldString == null || newString == null) { - throw new IllegalArgumentException("null argument"); - } - int pos = text.indexOf(oldString); - if (pos == -1) { - return text; - } - int lastPos = 0; - StringBuilder sb = new StringBuilder(text.length()); - while (pos != -1) { - sb.append(text.substring(lastPos, pos)); - sb.append(newString); - lastPos = pos + oldString.length(); - pos = text.indexOf(oldString, lastPos); - } - if (lastPos < text.length()) { - sb.append(text.substring(lastPos)); - } - return sb.toString(); - } - - /** - * Replaces XML characters in the given string that might need escaping as - * XML text or attribute - * - * @param text - * text to be escaped - * @return a string - */ - public static String encodeIllegalXMLCharacters(String text) { - return encodeMarkupCharacters(text, false); - } - - /** - * Replaces HTML characters in the given string that might need escaping as - * HTML text or attribute - * - * @param text - * text to be escaped - * @return a string - */ - public static String encodeIllegalHTMLCharacters(String text) { - return encodeMarkupCharacters(text, true); - } - - private static String encodeMarkupCharacters(String text, boolean isHtml) { - if (text == null) { - throw new IllegalArgumentException("null argument"); - } - StringBuilder buf = null; - int length = text.length(); - int pos = 0; - for (int i = 0; i < length; i++) { - int ch = text.charAt(i); - switch (ch) { - case '<': - case '>': - case '&': - case '"': - case '\'': - if (buf == null) { - buf = new StringBuilder(); - } - if (i > 0) { - buf.append(text.substring(pos, i)); - } - pos = i + 1; - break; - default: - continue; - } - if (ch == '<') { - buf.append("<"); - } else if (ch == '>') { - buf.append(">"); - } else if (ch == '&') { - buf.append("&"); - } else if (ch == '"') { - buf.append("""); - } else if (ch == '\'') { - buf.append(isHtml ? "'" : "'"); - } - } - if (buf == null) { - return text; - } else { - if (pos < length) { - buf.append(text.substring(pos)); - } - return buf.toString(); - } - } - - /** - * The list of characters that are not encoded by the escape() - * and unescape() METHODS. They contains the characters as - * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax': - *

- * - *

-	 * unreserved  = alphanum | mark
-	 * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
-	 * 
- */ - 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 string using the - * escape 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 string is null. - */ - public static String escape(String string, char escape) { - return escape(string, escape, false); - } - - /** - * Does an URL encoding of the string using the - * escape 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 isPath is - * true, additionally the slash '/' is ignored, too. - * - * @param string - * the string to encode. - * @param escape - * the escape character. - * @param isPath - * if true, the string is treated as path - * @return the escaped string - * @throws NullPointerException - * if string is null. - */ - 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 string. 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 string is null. - */ - public static String escape(String string) { - return escape(string, '%'); - } - - /** - * Does a URL encoding of the path. 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 path is null. - */ - public static String escapePath(String path) { - return escape(path, '%', true); - } - - /** - * Does a URL decoding of the string using the - * escape 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 string is null. - * @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 string. 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 string is null. - * @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. - *

- * QName EBNF:
- *

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 *) - * - * @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. - *

- * QName EBNF:
- *

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 *) - * - * @since Apache Jackrabbit 2.3.2 and 2.2.10 - * @see
JCR-3128 - * @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. - *

- * Example:
- * 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. - *

- * 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 null if path is - * null. - */ - public static String getName(String path) { - return getName(path, '/'); - } - - /** - * Returns the name part of the path, delimited by the given - * delim. If the given path is already a name (i.e. contains no - * delim characters) it is returned. - * - * @param path - * the path - * @param delim - * the delimiter - * @return the name part or null if path is - * null. - */ - 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 qname. If the - * prefix is missing, an empty string is returned. Please note, that this - * method does not validate the name or prefix. - *

- * 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 qname is null - */ - public static String getNamespacePrefix(String qname) { - int pos = qname.indexOf(':'); - return pos >= 0 ? qname.substring(0, pos) : ""; - } - - /** - * Returns the local name of the given qname. Please note, that - * this method does not validate the name. - *

- * the qname has the format: qname := [prefix ':'] local; - * - * @param qname - * a qualified name - * @return the localname - * - * @see #getNamespacePrefix(String) - * - * @throws NullPointerException - * if qname is null - */ - 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 descendant path is hierarchical a - * descendant of path. - * - * @param path - * the current path - * @param descendant - * the potential descendant - * @return true if the descendant is a descendant; - * false 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 descendant path is hierarchical a - * descendant of path or equal to it. - * - * @param path - * the path to check - * @param descendant - * the potential descendant - * @return true if the descendant is a descendant - * or equal; false 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 nth relative parent of the path, where n=level. - *

- * Example:
- * - * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar" - * - * - * @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 nth absolute parent of the path, where n=level. - *

- * Example:
- * - * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar" - * - * - * @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 - * ${...} 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 - * ignoreMissing is true. In the later case, the - * missing variable is replaced by the empty string. - * - * @param value - * the original value - * @param ignoreMissing - * if true, missing variables are replaced by the - * empty string. - * @return value after variable replacements - * @throws IllegalArgumentException - * if the replacement of a referenced variable is not found - */ - public static String replaceVariables(Properties variables, String value, boolean ignoreMissing) - throws IllegalArgumentException { - StringBuilder result = new StringBuilder(); - - // Value: - // +--+-+--------+-+-----------------+ - // | |p|--> |q|--> | - // +--+-+--------+-+-----------------+ - int p = 0, q = value.indexOf("${"); // Find first ${ - while (q != -1) { - result.append(value.substring(p, q)); // Text before ${ - p = q; - q = value.indexOf("}", q + 2); // Find } - if (q != -1) { - String variable = value.substring(p + 2, q); - String replacement = variables.getProperty(variable); - if (replacement == null) { - if (ignoreMissing) { - replacement = ""; - } else { - throw new IllegalArgumentException("Replacement not found for ${" + variable + "}."); - } - } - result.append(replacement); - p = q + 1; - q = value.indexOf("${", p); // Find next ${ - } - } - result.append(value.substring(p, value.length())); // Trailing text - - return result.toString(); - } - - private static byte decodeDigit(byte b) { - if (b >= 0x30 && b <= 0x39) { - return (byte) (b - 0x30); - } else if (b >= 0x41 && b <= 0x46) { - return (byte) (b - 0x37); - } else if (b >= 0x61 && b <= 0x66) { - return (byte) (b - 0x57); - } else { - throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java deleted file mode 100644 index ce4205a97..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.attribute.FileAttributeView; -import java.nio.file.attribute.FileStoreAttributeView; -import java.util.Arrays; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Workspace; - -import org.argeo.api.acr.fs.AbstractFsStore; -import org.argeo.jcr.JcrUtils; - -/** A {@link FileStore} implementation based on JCR {@link Workspace}. */ -public class WorkspaceFileStore extends AbstractFsStore { - private final String mountPath; - private final Workspace workspace; - private final String workspaceName; - private final int mountDepth; - - public WorkspaceFileStore(String mountPath, Workspace workspace) { - if ("/".equals(mountPath) || "".equals(mountPath)) - throw new IllegalArgumentException( - "Mount path '" + mountPath + "' is unsupported, use null for the base file store"); - if (mountPath != null && !mountPath.startsWith(JcrPath.separator)) - throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /"); - if (mountPath != null && mountPath.endsWith(JcrPath.separator)) - throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /"); - this.mountPath = mountPath; - if (mountPath == null) - mountDepth = 0; - else { - mountDepth = mountPath.split(JcrPath.separator).length - 1; - } - this.workspace = workspace; - this.workspaceName = workspace.getName(); - } - - public void close() { - JcrUtils.logoutQuietly(workspace.getSession()); - } - - @Override - public String name() { - return workspace.getName(); - } - - @Override - public String type() { - return "workspace"; - } - - @Override - public boolean isReadOnly() { - return false; - } - - @Override - public long getTotalSpace() throws IOException { - return 0; - } - - @Override - public long getUsableSpace() throws IOException { - return 0; - } - - @Override - public long getUnallocatedSpace() throws IOException { - return 0; - } - - @Override - public boolean supportsFileAttributeView(Class type) { - return false; - } - - @Override - public boolean supportsFileAttributeView(String name) { - return false; - } - - @Override - public V getFileStoreAttributeView(Class type) { - return null; - } - - @Override - public Object getAttribute(String attribute) throws IOException { - return workspace.getSession().getRepository().getDescriptor(attribute); - } - - public Workspace getWorkspace() { - return workspace; - } - - public String toFsPath(Node node) throws RepositoryException { - String nodeWorkspaceName = node.getSession().getWorkspace().getName(); - if (!nodeWorkspaceName.equals(workspace.getName())) - throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName - + "' in file store '" + workspace.getName() + "'"); - return mountPath == null ? node.getPath() : mountPath + node.getPath(); - } - - public boolean isBase() { - return mountPath == null; - } - - Node toNode(String[] fullPath) throws RepositoryException { - String jcrPath = toJcrPath(fullPath); - Session session = workspace.getSession(); - if (!session.itemExists(jcrPath)) - return null; - Node node = session.getNode(jcrPath); - return node; - } - - String toJcrPath(String fsPath) { - if (fsPath.length() == 1) - return toJcrPath((String[]) null);// root - String[] arr = fsPath.substring(1).split("/"); -// if (arr.length == 0 || (arr.length == 1 && arr[0].equals(""))) -// return toJcrPath((String[]) null);// root -// else - return toJcrPath(arr); - } - - private String toJcrPath(String[] path) { - if (path == null) - return "/"; - if (path.length < mountDepth) - throw new IllegalArgumentException( - "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath); - - if (!isBase()) { - // check mount compatibility - StringBuilder mount = new StringBuilder(); - mount.append('/'); - for (int i = 0; i < mountDepth; i++) { - if (i != 0) - mount.append('/'); - mount.append(Text.escapeIllegalJcrChars(path[i])); - } - if (!mountPath.equals(mount.toString())) - throw new IllegalArgumentException( - "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath); - } - - StringBuilder sb = new StringBuilder(); - sb.append('/'); - for (int i = mountDepth; i < path.length; i++) { - if (i != mountDepth) - sb.append('/'); - sb.append(Text.escapeIllegalJcrChars(path[i])); - } - return sb.toString(); - } - - public String getMountPath() { - return mountPath; - } - - public String getWorkspaceName() { - return workspaceName; - } - - public int getMountDepth() { - return mountDepth; - } - - @Override - public int hashCode() { - return workspaceName.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof WorkspaceFileStore)) - return false; - WorkspaceFileStore other = (WorkspaceFileStore) obj; - return workspaceName.equals(other.workspaceName); - } - - @Override - public String toString() { - return "WorkspaceFileStore " + workspaceName; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java deleted file mode 100644 index 0cdfdaf43..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Java NIO file system implementation based on plain JCR. */ -package org.argeo.jcr.fs; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd deleted file mode 100644 index 3eb0e7a3d..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd +++ /dev/null @@ -1,16 +0,0 @@ -// -// JCR EXTENSIONS -// - - -[jcrx:xmlvalue] -- * -+ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext - -[jcrx:xmltext] - - jcr:xmlcharacters (STRING) mandatory - -[jcrx:csum] -mixin - - jcrx:sum (STRING) * - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java deleted file mode 100644 index 1837749f1..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic JCR utilities. */ -package org.argeo.jcr; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java deleted file mode 100644 index 0177636f8..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.argeo.jcr.proxy; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -/** Base class for URL based proxys. */ -public abstract class AbstractUrlProxy implements ResourceProxy { - private final static CmsLog log = CmsLog.getLog(AbstractUrlProxy.class); - - private Repository jcrRepository; - private Session jcrAdminSession; - private String proxyWorkspace = "proxy"; - - protected abstract Node retrieve(Session session, String path); - - void init() { - try { - jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace); - beforeInitSessionSave(jcrAdminSession); - if (jcrAdminSession.hasPendingChanges()) - jcrAdminSession.save(); - } catch (RepositoryException e) { - JcrUtils.discardQuietly(jcrAdminSession); - throw new JcrException("Cannot initialize URL proxy", e); - } - } - - /** - * Called before the (admin) session is saved at the end of the initialization. - * Does nothing by default, to be overridden. - */ - protected void beforeInitSessionSave(Session session) throws RepositoryException { - } - - void destroy() { - JcrUtils.logoutQuietly(jcrAdminSession); - } - - /** - * Called before the (admin) session is logged out when resources are released. - * Does nothing by default, to be overridden. - */ - protected void beforeDestroySessionLogout() throws RepositoryException { - } - - public Node proxy(String path) { - // we open a JCR session with client credentials in order not to use the - // admin session in multiple thread or make it a bottleneck. - Node nodeAdmin = null; - Node nodeClient = null; - Session clientSession = null; - try { - clientSession = jcrRepository.login(proxyWorkspace); - if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) { - nodeAdmin = retrieveAndSave(path); - if (nodeAdmin != null) - nodeClient = clientSession.getNode(path); - } else - nodeClient = clientSession.getNode(path); - return nodeClient; - } catch (RepositoryException e) { - throw new JcrException("Cannot proxy " + path, e); - } finally { - if (nodeClient == null) - JcrUtils.logoutQuietly(clientSession); - } - } - - protected synchronized Node retrieveAndSave(String path) { - try { - Node node = retrieve(jcrAdminSession, path); - if (node == null) - return null; - jcrAdminSession.save(); - return node; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(jcrAdminSession); - throw new JcrException("Cannot retrieve and save " + path, e); - } finally { - notifyAll(); - } - } - - /** Session is not saved */ - protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException { - Node node = null; - if (session.itemExists(path)) { - // throw new ArgeoJcrException("Node " + path + " already exists"); - } - try (InputStream in = new URL(remoteUrl).openStream()) { - // URL u = new URL(remoteUrl); - // in = u.openStream(); - node = importFile(session, path, in); - } catch (IOException e) { - if (log.isDebugEnabled()) { - log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage()); - // log.trace("Cannot read because of ", e); - } - JcrUtils.discardQuietly(session); - // } finally { - // IOUtils.closeQuietly(in); - } - return node; - } - - protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException { - Binary binary = null; - try { - Node content = null; - Node node = null; - if (!session.itemExists(path)) { - node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false); - content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); - } else { - node = session.getNode(path); - content = node.getNode(Node.JCR_CONTENT); - } - binary = session.getValueFactory().createBinary(in); - content.setProperty(Property.JCR_DATA, binary); - JcrUtils.updateLastModifiedAndParents(node, null, true); - return node; - } finally { - JcrUtils.closeQuietly(binary); - } - } - - /** Whether the file should be updated. */ - protected Boolean shouldUpdate(Session clientSession, String nodePath) { - return false; - } - - public void setJcrRepository(Repository jcrRepository) { - this.jcrRepository = jcrRepository; - } - - public void setProxyWorkspace(String localWorkspace) { - this.proxyWorkspace = localWorkspace; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java deleted file mode 100644 index 84eea1f31..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java +++ /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, null if the resource was not found - * (e.g. HTTP 404) - */ - public Node proxy(String relativePath); -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java deleted file mode 100644 index a8e00df60..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.argeo.jcr.proxy; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.argeo.jcr.JcrException; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.Bin; -import org.argeo.jcr.JcrUtils; - -/** Wraps a proxy via HTTP */ -public class ResourceProxyServlet extends HttpServlet { - private static final long serialVersionUID = -8886549549223155801L; - - private final static CmsLog log = CmsLog - .getLog(ResourceProxyServlet.class); - - private ResourceProxy proxy; - - private String contentTypeCharset = "UTF-8"; - - @Override - protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - String path = request.getPathInfo(); - - if (log.isTraceEnabled()) { - log.trace("path=" + path); - log.trace("UserPrincipal = " + request.getUserPrincipal().getName()); - log.trace("SessionID = " + request.getSession(false).getId()); - log.trace("ContextPath = " + request.getContextPath()); - log.trace("ServletPath = " + request.getServletPath()); - log.trace("PathInfo = " + request.getPathInfo()); - log.trace("Method = " + request.getMethod()); - log.trace("User-Agent = " + request.getHeader("User-Agent")); - } - - Node node = null; - try { - node = proxy.proxy(path); - if (node == null) - response.sendError(404); - else - processResponse(node, response); - } finally { - if (node != null) - try { - JcrUtils.logoutQuietly(node.getSession()); - } catch (RepositoryException e) { - // silent - } - } - - } - - /** Retrieve the content of the node. */ - protected void processResponse(Node node, HttpServletResponse response) { -// Binary binary = null; -// InputStream in = null; - try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT) - .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) { - String fileName = node.getName(); - String ext = FilenameUtils.getExtension(fileName); - - // TODO use a more generic / standard approach - // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml - String contentType; - if ("xml".equals(ext)) - contentType = "text/xml;charset=" + contentTypeCharset; - else if ("jar".equals(ext)) - contentType = "application/java-archive"; - else if ("zip".equals(ext)) - contentType = "application/zip"; - else if ("gz".equals(ext)) - contentType = "application/x-gzip"; - else if ("bz2".equals(ext)) - contentType = "application/x-bzip2"; - else if ("tar".equals(ext)) - contentType = "application/x-tar"; - else if ("rpm".equals(ext)) - contentType = "application/x-redhat-package-manager"; - else - contentType = "application/octet-stream"; - contentType = contentType + ";name=\"" + fileName + "\""; - response.setHeader("Content-Disposition", "attachment; filename=\"" - + fileName + "\""); - response.setHeader("Expires", "0"); - response.setHeader("Cache-Control", "no-cache, must-revalidate"); - response.setHeader("Pragma", "no-cache"); - - response.setContentType(contentType); - - IOUtils.copy(in, response.getOutputStream()); - } catch (RepositoryException e) { - throw new JcrException("Cannot download " + node, e); - } catch (IOException e) { - throw new RuntimeException("Cannot download " + node, e); - } - } - - public void setProxy(ResourceProxy resourceProxy) { - this.proxy = resourceProxy; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java deleted file mode 100644 index a578c456e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Components to build proxys based on JCR. */ -package org.argeo.jcr.proxy; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java deleted file mode 100644 index 2adb6a97e..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java +++ /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 - * false. - */ - 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 null 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 - * false, 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 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 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("'); - } - - /** Singleton. */ - private JcrXmlUtils() { - - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl deleted file mode 100644 index 813d06570..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java deleted file mode 100644 index 3c8f296c4..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.argeo.maintenance; - -import java.io.IOException; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Set; - -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrUtils; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.util.naming.Distinguished; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -/** Make sure roles and access rights are properly configured. */ -public abstract class AbstractMaintenanceService { - private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class); - - private Repository repository; -// private UserAdminService userAdminService; - private UserAdmin userAdmin; - private WorkTransaction userTransaction; - - public void init() { - makeSureRolesExists(getRequiredRoles()); - configureStandardRoles(); - - Set 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 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 getRequiredRoles() { - return new HashSet<>(); - } - - public void destroy() { - - } - - /* - * UTILITIES - */ - - /** Create these roles as group if they don't exist. */ - protected void makeSureRolesExists(EnumSet enumSet) { - makeSureRolesExists(Distinguished.enumToDns(enumSet)); - } - - /** Create these roles as group if they don't exist. */ - protected void makeSureRolesExists(Set requiredRoles) { - if (requiredRoles == null) - return; - if (getUserAdmin() == null) { - log.warn("No user admin service available, cannot make sure that role exists"); - return; - } - for (String role : requiredRoles) { - Role systemRole = getUserAdmin().getRole(role); - if (systemRole == null) { - try { - getUserTransaction().begin(); - getUserAdmin().createRole(role, Role.GROUP); - getUserTransaction().commit(); - log.info("Created role " + role); - } catch (Exception e) { - try { - getUserTransaction().rollback(); - } catch (Exception e1) { - // silent - } - throw new IllegalStateException("Cannot create role " + role, e); - } - } - } - } - - /** Add a user or group to a group. */ - protected void addToGroup(String groupToAddDn, String groupDn) { - if (groupToAddDn.contentEquals(groupDn)) { - if (log.isTraceEnabled()) - log.trace("Ignore adding group " + groupDn + " to itself"); - return; - } - - if (getUserAdmin() == null) { - log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn); - return; - } - Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn); - if (groupToAdd == null) - throw new IllegalArgumentException("Group " + groupToAddDn + " not found"); - Group group = (Group) getUserAdmin().getRole(groupDn); - if (group == null) - throw new IllegalArgumentException("Group " + groupDn + " not found"); - try { - getUserTransaction().begin(); - if (group.addMember(groupToAdd)) - log.info("Added " + groupToAddDn + " to " + group); - getUserTransaction().commit(); - } catch (Exception e) { - try { - getUserTransaction().rollback(); - } catch (Exception e1) { - // silent - } - throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn); - } - } - - /* - * DEPENDENCY INJECTION - */ - public void setRepository(Repository repository) { - this.repository = repository; - } - -// public void setUserAdminService(UserAdminService userAdminService) { -// this.userAdminService = userAdminService; -// } - - protected WorkTransaction getUserTransaction() { - return userTransaction; - } - - protected UserAdmin getUserAdmin() { - return userAdmin; - } - - public void setUserAdmin(UserAdmin userAdmin) { - this.userAdmin = userAdmin; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java deleted file mode 100644 index ebb8c534d..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.argeo.maintenance; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.api.cms.CmsLog; -import org.argeo.osgi.transaction.WorkTransaction; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -/** - * Register one or many roles via a user admin service. Does nothing if the role - * is already registered. - */ -public class SimpleRoleRegistration implements Runnable { - private final static CmsLog log = CmsLog.getLog(SimpleRoleRegistration.class); - - private String role; - private List roles = new ArrayList(); - 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 roles) { - this.roles = roles; - } - - public void setUserAdmin(UserAdmin userAdminService) { - this.userAdmin = userAdminService; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java deleted file mode 100644 index ef83c1ff9..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java +++ /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 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("\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(""); - 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 getContentPaths() { - return contentPaths; - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java deleted file mode 100644 index 00d4be8b0..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java +++ /dev/null @@ -1,448 +0,0 @@ -package org.argeo.maintenance.backup; - -import java.io.BufferedWriter; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipOutputStream; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.apache.commons.io.IOUtils; -import org.apache.jackrabbit.api.JackrabbitSession; -import org.apache.jackrabbit.api.JackrabbitValue; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -/** - * Performs a backup of the data based only on programmatic interfaces. Useful - * for migration or live backup. Physical backups of the underlying file - * systems, databases, LDAP servers, etc. should be performed for disaster - * recovery. - */ -public class LogicalBackup implements Runnable { - private final static CmsLog log = CmsLog.getLog(LogicalBackup.class); - - public final static String WORKSPACES_BASE = "workspaces/"; - public final static String FILES_BASE = "files/"; - public final static String OSGI_BASE = "share/osgi/"; - - public final static String JCR_SYSTEM = "jcr:system"; - public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage"; - - private final Repository repository; - private String defaultWorkspace; - private final BundleContext bundleContext; - - private final ZipOutputStream zout; - private final Path basePath; - - private ExecutorService executorService; - - private boolean performSoftwareBackup = false; - - private Map 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> 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> contentPathsFuture = executorService - .submit(() -> performNodeBackup(defaultWorkspace, nodePath)); - executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture)); - } - } finally { - Jcr.logout(session); - } - - } - - protected Set 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 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> contentPathsFuture) { - Set 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 []"); - - } - - protected static Repository createRemoteRepository(URI uri) throws RepositoryException { - RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); - Map params = new HashMap(); - 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 headers = bundle.getHeaders(); - Manifest manifest = new Manifest(); - Enumeration 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 resourcePaths = bundle.findEntries("/", "*", true); - resources: while (resourcePaths.hasMoreElements()) { - URL entryUrl = resourcePaths.nextElement(); - String entryPath = entryUrl.getPath(); - if (entryPath.equals("")) - continue resources; - if (entryPath.endsWith("/")) - continue resources; - String entryName = entryPath.substring(1);// remove first '/' - if (entryUrl.getPath().equals("/META-INF/")) - continue resources; - if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF")) - continue resources; - // dev - if (entryUrl.getPath().startsWith("/target")) - continue resources; - if (entryUrl.getPath().startsWith("/src")) - continue resources; - if (entryUrl.getPath().startsWith("/ext")) - continue resources; - - if (entryName.startsWith("bin/")) {// dev - entryName = entryName.substring("bin/".length()); - } - - ZipEntry entry = new ZipEntry(entryName); - try (InputStream in = entryUrl.openStream()) { - try { - jarOut.putNextEntry(entry); - } catch (ZipException e) {// duplicate - continue resources; - } - IOUtils.copy(in, jarOut); - jarOut.closeEntry(); -// log.info(entryUrl); - } catch (FileNotFoundException e) { - log.warn(entryUrl + ": " + e.getMessage()); - } - } - } catch (IOException e1) { - throw new RuntimeException("Cannot export bundle " + bundle, e1); - } - } - if (log.isDebugEnabled()) - log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath); - - } - - protected synchronized void markBackupFailed(Object message, Exception e) { - log.error(message, e); - backupFailed = true; - notifyAll(); - if (executorService != null) - executorService.shutdownNow(); - } - - protected boolean isBackupFailed() { - return backupFailed; - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java deleted file mode 100644 index 122c96701..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.maintenance.backup; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; - -import javax.jcr.ImportUUIDBehavior; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.osgi.framework.BundleContext; - -/** Restores a backup in the format defined by {@link LogicalBackup}. */ -public class LogicalRestore implements Runnable { - private final static CmsLog log = CmsLog.getLog(LogicalRestore.class); - - private final Repository repository; - private final BundleContext bundleContext; - private final Path basePath; - - public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) { - this.repository = repository; - this.basePath = basePath; - this.bundleContext = bundleContext; - } - - @Override - public void run() { - Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE); - try { - // import jcr:system first -// Session defaultSession = NodeUtils.openDataAdminSession(repository, null); -// try (DirectoryStream 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 workspaceDirs = Files.newDirectoryStream(workspaces)) { - for (Path workspacePath : workspaceDirs) { - String workspaceName = workspacePath.getFileName().toString(); - Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName); - try (DirectoryStream xmls = Files.newDirectoryStream(workspacePath, "*.xml")) { - xmls: for (Path xml : xmls) { - if (xml.getFileName().toString().startsWith("rep:")) - continue xmls; - try (InputStream in = Files.newInputStream(xml)) { - session.getWorkspace().importXML("/", in, - ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); - if (log.isDebugEnabled()) - log.debug("Restored " + xml + " to workspace " + workspaceName); - } - } - } finally { - Jcr.logout(session); - } - } - } - } catch (IOException e) { - throw new RuntimeException("Cannot restore backup from " + basePath, e); - } catch (RepositoryException e) { - throw new JcrException("Cannot restore backup from " + basePath, e); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java deleted file mode 100644 index a61e19bd4..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo Node backup utilities. */ -package org.argeo.maintenance.backup; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java deleted file mode 100644 index ef40ab3a3..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.maintenance.internal; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import javax.jcr.Repository; - -import org.argeo.maintenance.backup.LogicalBackup; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -public class Activator implements BundleActivator { - - @Override - public void start(BundleContext context) throws Exception { - // Start backup - Repository repository = context.getService(context.getServiceReference(Repository.class)); - Path basePath = Paths.get(System.getProperty("user.dir"), "backup"); - LogicalBackup backup = new LogicalBackup(context, repository, basePath); - backup.run(); - } - - @Override - public void stop(BundleContext context) throws Exception { - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java deleted file mode 100644 index 1ce974c6f..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Utilities for the maintenance of an Argeo Node. */ -package org.argeo.maintenance; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java deleted file mode 100644 index bffe531a1..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java +++ /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 principals) throws RepositoryException { - return super.canAccessRoot(principals); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java deleted file mode 100644 index 7464078d8..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.security.jackrabbit; - -import javax.jcr.PathNotFoundException; -import javax.jcr.RepositoryException; -import javax.jcr.security.Privilege; - -import org.apache.jackrabbit.core.id.ItemId; -import org.apache.jackrabbit.core.security.DefaultAccessManager; -import org.apache.jackrabbit.spi.Path; - -/** - * Intermediary class in order to have a consistent naming in config files. Does - * nothing for the time being, but may in the future. - */ -public class ArgeoAccessManager extends DefaultAccessManager { - - @Override - public boolean canRead(Path itemPath, ItemId itemId) - throws RepositoryException { - return super.canRead(itemPath, itemId); - } - - @Override - public Privilege[] getPrivileges(String absPath) - throws PathNotFoundException, RepositoryException { - return super.getPrivileges(absPath); - } - - @Override - public boolean hasPrivileges(String absPath, Privilege[] privileges) - throws PathNotFoundException, RepositoryException { - return super.hasPrivileges(absPath, privileges); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java deleted file mode 100644 index d679c45f9..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.security.jackrabbit; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.jackrabbit.core.security.authentication.AuthContext; - -/** Wraps a regular {@link LoginContext}, using the proper class loader. */ -class ArgeoAuthContext implements AuthContext { - private LoginContext lc; - - public ArgeoAuthContext(String appName, Subject subject, CallbackHandler callbackHandler) { - try { - lc = new LoginContext(appName, subject, callbackHandler); - } catch (LoginException e) { - throw new IllegalStateException("Cannot configure Jackrabbit login context", e); - } - } - - @Override - public void login() throws LoginException { - lc.login(); - } - - @Override - public Subject getSubject() { - return lc.getSubject(); - } - - @Override - public void logout() throws LoginException { - lc.logout(); - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java deleted file mode 100644 index 36ee547e5..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.argeo.security.jackrabbit; - -import java.security.Principal; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; - -import javax.jcr.Credentials; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.x500.X500Principal; - -import org.apache.jackrabbit.api.security.user.UserManager; -import org.apache.jackrabbit.core.DefaultSecurityManager; -import org.apache.jackrabbit.core.security.AMContext; -import org.apache.jackrabbit.core.security.AccessManager; -import org.apache.jackrabbit.core.security.SecurityConstants; -import org.apache.jackrabbit.core.security.SystemPrincipal; -import org.apache.jackrabbit.core.security.authentication.AuthContext; -import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl; -import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager; -import org.apache.jackrabbit.core.security.principal.AdminPrincipal; -import org.apache.jackrabbit.core.security.principal.PrincipalProvider; -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.DataAdminPrincipal; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.AnonymousPrincipal; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** Customises Jackrabbit security. */ -public class ArgeoSecurityManager extends DefaultSecurityManager { - private final static CmsLog log = CmsLog.getLog(ArgeoSecurityManager.class); - - private BundleContext cmsBundleContext = null; - - public ArgeoSecurityManager() { - if (FrameworkUtil.getBundle(CmsSession.class) != null) { - cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext(); - } - } - - public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) - throws RepositoryException { - checkInitialized(); - - CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(), - adminId, anonymousId); - String appName = "Jackrabbit"; - return new ArgeoAuthContext(appName, subject, cbHandler); - } - - @Override - public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { - synchronized (getSystemSession()) { - return super.getAccessManager(session, amContext); - } - } - - @Override - public UserManager getUserManager(Session session) throws RepositoryException { - synchronized (getSystemSession()) { - return super.getUserManager(session); - } - } - - @Override - protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { - return super.createDefaultPrincipalProvider(moduleConfig); - } - - /** Called once when the session is created */ - @Override - public String getUserID(Subject subject, String workspaceName) throws RepositoryException { - boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty(); - boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty(); - boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty(); - Set 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 principals, String workspaceName) throws RepositoryException { - // TODO: implements finer access to workspaces - if (log.isTraceEnabled()) - log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'"); - return true; - // return wam.grants(principals, workspaceName); - } - } - -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java deleted file mode 100644 index 0f63957b7..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.argeo.security.jackrabbit; - -import java.util.Map; -import java.util.Set; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; -import javax.security.auth.x500.X500Principal; - -import org.apache.jackrabbit.core.security.AnonymousPrincipal; -import org.apache.jackrabbit.core.security.SecurityConstants; -import org.apache.jackrabbit.core.security.principal.AdminPrincipal; -import org.argeo.api.cms.DataAdminPrincipal; - -/** JAAS login module used when initiating a new Jackrabbit session. */ -public class SystemJackrabbitLoginModule implements LoginModule { - private Subject subject; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, - Map options) { - this.subject = subject; - } - - @Override - public boolean login() throws LoginException { - return true; - } - - @Override - public boolean commit() throws LoginException { - Set anonPrincipal = subject - .getPrincipals(org.argeo.api.cms.AnonymousPrincipal.class); - if (!anonPrincipal.isEmpty()) { - subject.getPrincipals().add(new AnonymousPrincipal()); - return true; - } - - Set initPrincipal = subject.getPrincipals(DataAdminPrincipal.class); - if (!initPrincipal.isEmpty()) { - subject.getPrincipals().add(new AdminPrincipal(SecurityConstants.ADMIN_ID)); - return true; - } - - Set userPrincipal = subject.getPrincipals(X500Principal.class); - if (userPrincipal.isEmpty()) - throw new LoginException("Subject must be pre-authenticated"); - if (userPrincipal.size() > 1) - throw new LoginException("Multiple user principals " + userPrincipal); - - return true; - } - - @Override - public boolean abort() throws LoginException { - return true; - } - - @Override - public boolean logout() throws LoginException { - subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class)); - subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class)); - return true; - } -} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java deleted file mode 100644 index 8529cc207..000000000 --- a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Integration of Jackrabbit with Argeo security model. */ -package org.argeo.security.jackrabbit; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/.classpath b/jcr/org.argeo.cms.ui/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/jcr/org.argeo.cms.ui/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/jcr/org.argeo.cms.ui/.project b/jcr/org.argeo.cms.ui/.project deleted file mode 100644 index e52eb8ef7..000000000 --- a/jcr/org.argeo.cms.ui/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.ui - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/jcr/org.argeo.cms.ui/META-INF/.gitignore b/jcr/org.argeo.cms.ui/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/jcr/org.argeo.cms.ui/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/jcr/org.argeo.cms.ui/bnd.bnd b/jcr/org.argeo.cms.ui/bnd.bnd deleted file mode 100644 index c3c609c70..000000000 --- a/jcr/org.argeo.cms.ui/bnd.bnd +++ /dev/null @@ -1,23 +0,0 @@ -Bundle-Activator: org.argeo.cms.ui.internal.Activator -Bundle-ActivationPolicy: lazy - -Import-Package: org.eclipse.swt,\ -org.eclipse.jface.window,\ -org.eclipse.core.commands,\ -javax.jcr.security,\ -org.eclipse.rap.fileupload;version="[2.1,4)",\ -org.eclipse.rap.rwt;version="[2.1,4)",\ -org.eclipse.rap.rwt.application;version="[2.1,4)",\ -org.eclipse.rap.rwt.client;version="[2.1,4)",\ -org.eclipse.rap.rwt.client.service;version="[2.1,4)",\ -org.eclipse.rap.rwt.service;version="[2.1,4)",\ -org.eclipse.rap.rwt.widgets;version="[2.1,4)",\ -org.osgi.*;version=0.0.0,\ -javax.servlet.*;version="[3,5)",\ -* - -## TODO: in order to enable single sourcing, we have introduced dummy RAP packages -# in the RCP specific bundle org.argeo.eclipse.ui.rap. -# this was working with RAP 2.x but since we upgrade Rap to 3.1.x, -# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x -# We enable large RAP version range as a workaround that must be fixed \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/build.properties b/jcr/org.argeo.cms.ui/build.properties deleted file mode 100644 index c6baffa00..000000000 --- a/jcr/org.argeo.cms.ui/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - icons/ diff --git a/jcr/org.argeo.cms.ui/icons/loading.gif b/jcr/org.argeo.cms.ui/icons/loading.gif deleted file mode 100644 index 3288d1035..000000000 Binary files a/jcr/org.argeo.cms.ui/icons/loading.gif and /dev/null differ diff --git a/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png b/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png deleted file mode 100644 index 039650638..000000000 Binary files a/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png and /dev/null differ diff --git a/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png b/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png deleted file mode 100644 index 8e3abb518..000000000 Binary files a/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png and /dev/null differ diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java deleted file mode 100644 index 872142bca..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.ui; - -import java.util.EventObject; - -/** Notify of the edition lifecycle */ -public class CmsEditionEvent extends EventObject { - private static final long serialVersionUID = 950914736016693110L; - - public final static Integer START_EDITING = 0; - public final static Integer STOP_EDITING = 1; - - private final Integer type; - - public CmsEditionEvent(Object source, Integer type) { - super(source); - this.type = type; - } - - public Integer getType() { - return type; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java deleted file mode 100644 index 9df61dcca..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.ui; - -import org.argeo.api.cms.Cms2DSize; - -/** Commons constants */ -@Deprecated -public interface CmsUiConstants { - // DATAKEYS -// public final static String STYLE = EclipseUiConstants.CSS_CLASS; -// public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT; - @Deprecated - /* RWT.CUSTOM_ITEM_HEIGHT */ - public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight"; - - // EVENT DETAILS - @Deprecated - /* RWT.HYPERLINK */ - public final static int HYPERLINK = 1 << 26; - - // STANDARD RESOURCES - public final static String LOADING_IMAGE = "icons/loading.gif"; - - public final static String NO_IMAGE = "icons/noPic-square-640px.png"; - public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320); - public final static Float NO_IMAGE_RATIO = 1f; - // MISCEALLENEOUS - String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm"; -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java deleted file mode 100644 index ec76321fe..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.argeo.cms.ui; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.MvcProvider; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Stateless factory building an SWT user interface given a JCR context. */ -@FunctionalInterface -public interface CmsUiProvider extends MvcProvider { - /** - * Initialises a user interface. - * - * @param parent the parent composite - * @param context a context node (holding the JCR underlying session), or null - */ - Control createUi(Composite parent, Node context) throws RepositoryException; - - @Override - default Control createUiPart(Composite parent, Node context) { - try { - return createUi(parent, context); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create UI for context " + context, e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java deleted file mode 100644 index 5d77c156c..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.cms.ui; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -/** CmsUiProvider notified of initialisation with a system session. */ -public interface LifeCycleUiProvider extends CmsUiProvider { - public void init(Session adminSession) throws RepositoryException; - - public void destroy(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java deleted file mode 100644 index 4ce468826..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java +++ /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 - * true 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 false - */ - 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 true if the part is dirty, false - * 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 true if the part is stale, false - * otherwise. - */ - public boolean isStale() { - return stale; - } - /** - * Marks the part stale. Subclasses should call this method as a result of - * model notification that indicates that the content of the section is no - * longer in sync with the model. - */ - public void markStale() { - stale = true; - managedForm.staleStateChanged(); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java deleted file mode 100644 index 32b031b86..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java +++ /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 IFormColors.TITLE. - */ - public static final String TITLE = IFormColors.TITLE; - - /** - * Key for the tree/table border color. - * - * @deprecated use IFormColors.BORDER - */ - public static final String BORDER = IFormColors.BORDER; - - /** - * Key for the section separator color. - * - * @deprecated use IFormColors.SEPARATOR. - */ - public static final String SEPARATOR = IFormColors.SEPARATOR; - - /** - * Key for the section title bar background. - * - * @deprecated use IFormColors.TB_BG - */ - public static final String TB_BG = IFormColors.TB_BG; - - /** - * Key for the section title bar foreground. - * - * @deprecated use IFormColors.TB_FG - */ - public static final String TB_FG = IFormColors.TB_FG; - - /** - * Key for the section title bar gradient. - * - * @deprecated use IFormColors.TB_GBG - */ - public static final String TB_GBG = IFormColors.TB_GBG; - - /** - * Key for the section title bar border. - * - * @deprecated use IFormColors.TB_BORDER. - */ - 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 IFormColors.TB_TOGGLE. - */ - public static final String TB_TOGGLE = IFormColors.TB_TOGGLE; - - /** - * Key for the section toggle hover color. - * - * @deprecated use IFormColors.TB_TOGGLE_HOVER. - */ - 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 initializeColorTable(). - * - * @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 SWT class. - * - * @param code - * the system color constant as defined in SWT - * 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. - * - *

- * 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 true if background is white, false - * 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 null if - * not in the registry. - * - * @param key - * the color key - * @return color object if found, or null 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 true if shared, false 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 true if at least one of the primary colors in the - * source RGB are within the provided range, false - * 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 true if at least two of the primary colors in the - * source RGB are within the provided range, false - * otherwise. - */ - public static boolean testTwoPrimaryColors(RGB rgb, int from, int to) { - int total = 0; - if (testPrimaryColor(rgb.red, from, to)) - total++; - if (testPrimaryColor(rgb.green, from, to)) - total++; - if (testPrimaryColor(rgb.blue, from, to)) - total++; - return total >= 2; - } - - /** - * Blends two primary color components based on the provided ratio. - * - * @param v1 - * first component - * @param v2 - * second component - * @param ratio - * percentage of the first component in the blend - * @return - */ - private static int blend(int v1, int v2, int ratio) { - int b = (ratio * v1 + (100 - ratio) * v2) / 100; - return Math.min(255, b); - } - - private Color getImpliedBackground() { - if (getBackground() != null) - return getBackground(); - return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); - } - - private static boolean testPrimaryColor(int value, int from, int to) { - return value > from && value < to; - } - - private void createTitleColor() { - /* - * RGB rgb = getSystemColor(SWT.COLOR_LIST_SELECTION); // test too light - * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80); - * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK, - * 50); createColor(TITLE, rgb); - */ - RGB bg = getImpliedBackground().getRGB(); - RGB listSelection = getSystemColor(SWT.COLOR_LIST_SELECTION); - RGB listForeground = getSystemColor(SWT.COLOR_LIST_FOREGROUND); - RGB rgb = listSelection; - - // Group 1 - // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or - // between 0 and 120, then use 100% LIST_SELECTION as it is (no - // additions) - // Examples: XP Default, Win Classic Standard, Win High Con White, Win - // Classic Marine - if (testTwoPrimaryColors(listSelection, -1, 121)) - rgb = listSelection; - // Group 2 - // When LIST_BACKGROUND = white (255, 255, 255) or not black, text - // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over - // LIST_BACKGROUND - // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or - // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION - // foreground colour - // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX - // Aqua, OSX Graphite, Linux GTK - else if (testTwoPrimaryColors(listSelection, 120, 256) - || (bg.red == 0 && bg.green == 0 && bg.blue == 0)) - rgb = blend(listSelection, listForeground, 50); - // Group 3 - // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION - // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND - // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to - // LIST_SELECTION foreground colour - // Examples: Win High Con Black, Win High Con #1, Win High Con #2 - // (covered in the second part of the OR clause above) - createColor(IFormColors.TITLE, rgb); - } - - private void createTwistieColors() { - RGB rgb = getColor(IFormColors.TITLE).getRGB(); - RGB white = getSystemColor(SWT.COLOR_WHITE); - createColor(TB_TOGGLE, rgb); - rgb = blend(rgb, white, 60); - createColor(TB_TOGGLE_HOVER, rgb); - } - - private void createTitleBarGradientColors() { - RGB tbBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - RGB bg = getImpliedBackground().getRGB(); - - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(tbBg, 179, 256)) - tbBg = blend(tbBg, bg, 30); - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(tbBg, 120, 180)) - tbBg = blend(tbBg, bg, 20); - - // Group 3 - // Rule: Everything else - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else { - tbBg = blend(tbBg, bg, 10); - } - - createColor(IFormColors.TB_BG, tbBg); - - // for backward compatibility - createColor(TB_GBG, tbBg); - } - - private void createTitleBarOutlineColors() { - // title bar outline - border color - RGB tbBorder = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - RGB bg = getImpliedBackground().getRGB(); - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(tbBorder, 179, 256)) - tbBorder = blend(tbBorder, bg, 70); - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - - // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(tbBorder, 120, 180)) - tbBorder = blend(tbBorder, bg, 50); - - // Group 3 - // Rule: Everything else - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - - // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - else { - tbBorder = blend(tbBorder, bg, 30); - } - createColor(FormColors.TB_BORDER, tbBorder); - } - - private void updateFormHeaderColors() { - if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) { - disposeIfFound(IFormColors.H_GRADIENT_END); - disposeIfFound(IFormColors.H_GRADIENT_START); - disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1); - disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2); - disposeIfFound(IFormColors.H_HOVER_LIGHT); - disposeIfFound(IFormColors.H_HOVER_FULL); - initializeFormHeaderColors(); - } - } - - private void disposeIfFound(String key) { - Color color = getColor(key); - if (color != null) { - colorRegistry.remove(key); - // RAP [rh] changes due to missing Color#dispose() -// color.dispose(); - } - } - - private void createFormHeaderColors() { - createFormHeaderGradientColors(); - createFormHeaderKeylineColors(); - createFormHeaderDNDColors(); - } - - private void createFormHeaderGradientColors() { - RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - Color bgColor = getImpliedBackground(); - RGB bg = bgColor.getRGB(); - RGB bottom, top; - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(titleBg, 179, 256)) { - bottom = blend(titleBg, bg, 30); - top = bg; - } - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(titleBg, 120, 180)) { - bottom = blend(titleBg, bg, 20); - top = bg; - } - - // Group 3 - // Rule: If at least 2 of the RGB values are equal to or between 0 and - // 120, then apply specified opacity for Group 3 - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else { - bottom = blend(titleBg, bg, 10); - top = bg; - } - createColor(IFormColors.H_GRADIENT_END, top); - createColor(IFormColors.H_GRADIENT_START, bottom); - } - - private void createFormHeaderKeylineColors() { - RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - Color bgColor = getImpliedBackground(); - RGB bg = bgColor.getRGB(); - RGB keyline2; - // H_BOTTOM_KEYLINE1 - createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255)); - - // H_BOTTOM_KEYLINE2 - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(titleBg, 179, 256)) - keyline2 = blend(titleBg, bg, 70); - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(titleBg, 120, 180)) - keyline2 = blend(titleBg, bg, 50); - - // Group 3 - // Rule: If at least 2 of the RGB values are equal to or between 0 and - // 120, then apply specified opacity for Group 3 - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - - // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - else - keyline2 = blend(titleBg, bg, 30); - // H_BOTTOM_KEYLINE2 - createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2); - } - - private void createFormHeaderDNDColors() { - RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT); - Color bgColor = getImpliedBackground(); - RGB bg = bgColor.getRGB(); - RGB light, full; - // ALL Themes - // - // Light Highlight - // When *near* the 'hot' area - // Rule: If near the title in the 'hot' area, show background highlight - // TITLE_BACKGROUND_GRADIENT @ 40% - light = blend(titleBg, bg, 40); - // Full Highlight - // When *on* the title area (regions 1 and 2) - // Rule: If near the title in the 'hot' area, show background highlight - // TITLE_BACKGROUND_GRADIENT @ 60% - full = blend(titleBg, bg, 60); - // H_DND_LIGHT - // H_DND_FULL - createColor(IFormColors.H_HOVER_LIGHT, light); - createColor(IFormColors.H_HOVER_FULL, full); - } - - private LocalResourceManager getResourceManager() { - if (resources == null) - resources = new LocalResourceManager(JFaceResources.getResources()); - return resources; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java deleted file mode 100644 index 9e931ba50..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import java.util.HashMap; - -import org.eclipse.jface.resource.DeviceResourceException; -import org.eclipse.jface.resource.FontDescriptor; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.FontData; -//import org.eclipse.swt.internal.graphics.Graphics; -import org.eclipse.swt.widgets.Display; - -public class FormFonts { - private static FormFonts instance; - - public static FormFonts getInstance() { - if (instance == null) - instance = new FormFonts(); - return instance; - } - - private LocalResourceManager resources; - private HashMap descriptors; - - private FormFonts() { - } - - private class BoldFontDescriptor extends FontDescriptor { - private FontData[] fFontData; - - BoldFontDescriptor(Font font) { - // RAP [if] Changes due to different way of creating fonts - // fFontData = font.getFontData(); - // for (int i = 0; i < fFontData.length; i++) { - // fFontData[i].setStyle(fFontData[i].getStyle() | SWT.BOLD); - // } - FontData fontData = font.getFontData()[0]; - // Font boldFont = Graphics.getFont( fontData.getName(), - // fontData.getHeight(), - // fontData.getStyle() | SWT.BOLD ); - Font boldFont = new Font(Display.getCurrent(), fontData.getName(), fontData.getHeight(), - fontData.getStyle() | SWT.BOLD); - fFontData = boldFont.getFontData(); - } - - public boolean equals(Object obj) { - if (obj instanceof BoldFontDescriptor) { - BoldFontDescriptor desc = (BoldFontDescriptor) obj; - if (desc.fFontData.length != fFontData.length) - return false; - for (int i = 0; i < fFontData.length; i++) - if (!fFontData[i].equals(desc.fFontData[i])) - return false; - return true; - } - return false; - } - - public int hashCode() { - int hash = 0; - for (int i = 0; i < fFontData.length; i++) - hash = hash * 7 + fFontData[i].hashCode(); - return hash; - } - - public Font createFont(Device device) throws DeviceResourceException { - // RAP [if] Changes due to different way of creating fonts - return new Font(device, fFontData[0]); - // return Graphics.getFont( fFontData[ 0 ] ); - } - - public void destroyFont(Font previouslyCreatedFont) { - // RAP [if] unnecessary - // previouslyCreatedFont.dispose(); - } - } - - public Font getBoldFont(Display display, Font font) { - checkHashMaps(); - BoldFontDescriptor desc = new BoldFontDescriptor(font); - Font result = getResourceManager().createFont(desc); - descriptors.put(result, desc); - return result; - } - - public boolean markFinished(Font boldFont) { - checkHashMaps(); - BoldFontDescriptor desc = (BoldFontDescriptor) descriptors.get(boldFont); - if (desc != null) { - getResourceManager().destroyFont(desc); - if (getResourceManager().find(desc) == null) { - descriptors.remove(boldFont); - validateHashMaps(); - } - return true; - - } - // if the image was not found, dispose of it for the caller - // RAP [if] unnecessary - // boldFont.dispose(); - return false; - } - - private LocalResourceManager getResourceManager() { - if (resources == null) - resources = new LocalResourceManager(JFaceResources.getResources()); - return resources; - } - - private void checkHashMaps() { - if (descriptors == null) - descriptors = new HashMap(); - } - - private void validateHashMaps() { - if (descriptors.size() == 0) - descriptors = null; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java deleted file mode 100644 index 99271046a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java +++ /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. - *

- * 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. - *

- * The toolkit creates some of the most common controls used to populate Eclipse - * forms. Controls that must be created using their constructors, - * adapt() method is available to change its properties in the - * same way as with the supported toolkit controls. - *

- * 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). - *

- * FormToolkit is normally instantiated, but can also be subclassed if some of - * the methods needs to be modified. In those cases, super 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). - *

- * 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 not marked as - * shared via the markShared() method. - *

- * 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 null) - * @param style - * the button style (for example, SWT.PUSH) - * @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 true, 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 true, form will be scrolled horizontally - * and/or vertically if needed to ensure that the control is - * visible when it gains focus. Set it to false if - * the control is not capable of gaining focus. - * @param trackKeyboard - * if true, 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 - * false 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. -// *

-// * 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: -// * -// *

-//	 *
-//	 *
-//	 *
-//	 *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
-//	 *
-//	 *             or
-//	 *
-//	 *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
-//	 *
-//	 *
-//	 *
-//	 * 
-// * -// * @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 - * SWT.BORDER or SWT.NULL - * @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 true, 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 SWT.NULL, - * SWT.LEFT_TO_RIGHT and SWT.RIGHT_TO_LEFT. - * - * @return orientation style for this toolkit, or SWT.NULL 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 SWT.NULL, SWT.LEFT_TO_RIGHT - * and SWT.RIGHT_TO_LEFT. - * - * @param orientation - * style for this toolkit. - */ - - public void setOrientation(int orientation) { - this.orientation = orientation; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java deleted file mode 100644 index 76e3f1104..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java +++ /dev/null @@ -1,522 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.MouseEvent; -//import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.FontMetrics; -import org.eclipse.swt.graphics.GC; -//import org.eclipse.swt.graphics.Image; -//import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Layout; -//import org.eclipse.swt.widgets.ScrollBar; -import org.eclipse.swt.widgets.Text; -//import org.eclipse.ui.forms.widgets.ColumnLayout; -//import org.eclipse.ui.forms.widgets.Form; -//import org.eclipse.ui.forms.widgets.FormText; -//import org.eclipse.ui.forms.widgets.FormToolkit; -//import org.eclipse.ui.forms.widgets.ILayoutExtension; -// -//import com.ibm.icu.text.BreakIterator; - -public class FormUtil { - public static final String PLUGIN_ID = "org.eclipse.ui.forms"; //$NON-NLS-1$ - - static final int H_SCROLL_INCREMENT = 5; - - static final int V_SCROLL_INCREMENT = 64; - - public static final String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$ - - public static final String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$ - public static final String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$ - - public static final String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$ - - public static final String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$ - - public static final String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$ - - public static Text createText(Composite parent, String label, - FormToolkit factory) { - return createText(parent, label, factory, 1); - } - - public static Text createText(Composite parent, String label, - FormToolkit factory, int span) { - factory.createLabel(parent, label); - Text text = factory.createText(parent, ""); //$NON-NLS-1$ - int hfill = span == 1 ? GridData.FILL_HORIZONTAL - : GridData.HORIZONTAL_ALIGN_FILL; - GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); - gd.horizontalSpan = span; - text.setLayoutData(gd); - return text; - } - - public static Text createText(Composite parent, String label, - FormToolkit factory, int span, int style) { - Label l = factory.createLabel(parent, label); - if ((style & SWT.MULTI) != 0) { - GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING); - l.setLayoutData(gd); - } - Text text = factory.createText(parent, "", style); //$NON-NLS-1$ - int hfill = span == 1 ? GridData.FILL_HORIZONTAL - : GridData.HORIZONTAL_ALIGN_FILL; - GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); - gd.horizontalSpan = span; - text.setLayoutData(gd); - return text; - } - - public static Text createText(Composite parent, FormToolkit factory, - int span) { - Text text = factory.createText(parent, ""); //$NON-NLS-1$ - int hfill = span == 1 ? GridData.FILL_HORIZONTAL - : GridData.HORIZONTAL_ALIGN_FILL; - GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); - gd.horizontalSpan = span; - text.setLayoutData(gd); - return text; - } - - public static int computeMinimumWidth(GC gc, String text) { -// BreakIterator wb = BreakIterator.getWordInstance(); -// wb.setText(text); -// int last = 0; -// -// int width = 0; -// -// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { -// String word = text.substring(last, loc); -// Point extent = gc.textExtent(word); -// width = Math.max(width, extent.x); -// last = loc; -// } -// String lastWord = text.substring(last); -// Point extent = gc.textExtent(lastWord); -// width = Math.max(width, extent.x); -// return width; - return 0; - } - - public static Point computeWrapSize(GC gc, String text, int wHint) { -// BreakIterator wb = BreakIterator.getWordInstance(); -// wb.setText(text); - FontMetrics fm = gc.getFontMetrics(); - int lineHeight = fm.getHeight(); - - int saved = 0; - int last = 0; - int height = lineHeight; - int maxWidth = 0; -// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { -// String word = text.substring(saved, loc); -// Point extent = gc.textExtent(word); -// if (extent.x > wHint) { -// // overflow -// saved = last; -// height += extent.y; -// // switch to current word so maxWidth will accommodate very long single words -// word = text.substring(last, loc); -// extent = gc.textExtent(word); -// } -// maxWidth = Math.max(maxWidth, extent.x); -// last = loc; -// } - /* - * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth. - * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made - * to estimate the height, but the algorithm needs to be run again to be sure. - */ - if (maxWidth > wHint) - return computeWrapSize(gc, text, maxWidth); - return new Point(maxWidth, height); - } - -// RAP [rh] paintWrapText unnecessary -// public static void paintWrapText(GC gc, String text, Rectangle bounds) { -// paintWrapText(gc, text, bounds, false); -// } - -// RAP [rh] paintWrapText unnecessary -// public static void paintWrapText(GC gc, String text, Rectangle bounds, -// boolean underline) { -// BreakIterator wb = BreakIterator.getWordInstance(); -// wb.setText(text); -// FontMetrics fm = gc.getFontMetrics(); -// int lineHeight = fm.getHeight(); -// int descent = fm.getDescent(); -// -// int saved = 0; -// int last = 0; -// int y = bounds.y; -// int width = bounds.width; -// -// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { -// String line = text.substring(saved, loc); -// Point extent = gc.textExtent(line); -// -// if (extent.x > width) { -// // overflow -// String prevLine = text.substring(saved, last); -// gc.drawText(prevLine, bounds.x, y, true); -// if (underline) { -// Point prevExtent = gc.textExtent(prevLine); -// int lineY = y + lineHeight - descent + 1; -// gc -// .drawLine(bounds.x, lineY, bounds.x + prevExtent.x, -// lineY); -// } -// -// saved = last; -// y += lineHeight; -// } -// last = loc; -// } -// // paint the last line -// String lastLine = text.substring(saved, last); -// gc.drawText(lastLine, bounds.x, y, true); -// if (underline) { -// int lineY = y + lineHeight - descent + 1; -// Point lastExtent = gc.textExtent(lastLine); -// gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY); -// } -// } - - public static ScrolledComposite getScrolledComposite(Control c) { - Composite parent = c.getParent(); - - while (parent != null) { - if (parent instanceof ScrolledComposite) { - return (ScrolledComposite) parent; - } - parent = parent.getParent(); - } - return null; - } - - public static void ensureVisible(Control c) { - ScrolledComposite scomp = getScrolledComposite(c); - if (scomp != null) { - Object data = scomp.getData(FOCUS_SCROLLING); - if (data == null || !data.equals(Boolean.FALSE)) - FormUtil.ensureVisible(scomp, c); - } - } - - public static void ensureVisible(ScrolledComposite scomp, Control control) { - // if the control is a FormText we do not need to scroll since it will - // ensure visibility of its segments as necessary -// if (control instanceof FormText) -// return; - Point controlSize = control.getSize(); - Point controlOrigin = getControlLocation(scomp, control); - ensureVisible(scomp, controlOrigin, controlSize); - } - - public static void ensureVisible(ScrolledComposite scomp, - Point controlOrigin, Point controlSize) { - Rectangle area = scomp.getClientArea(); - Point scompOrigin = scomp.getOrigin(); - - int x = scompOrigin.x; - int y = scompOrigin.y; - - // horizontal right, but only if the control is smaller - // than the client area - if (controlSize.x < area.width - && (controlOrigin.x + controlSize.x > scompOrigin.x - + area.width)) { - x = controlOrigin.x + controlSize.x - area.width; - } - // horizontal left - make sure the left edge of - // the control is showing - if (controlOrigin.x < x) { - if (controlSize.x < area.width) - x = controlOrigin.x + controlSize.x - area.width; - else - x = controlOrigin.x; - } - // vertical bottom - if (controlSize.y < area.height - && (controlOrigin.y + controlSize.y > scompOrigin.y - + area.height)) { - y = controlOrigin.y + controlSize.y - area.height; - } - // vertical top - make sure the top of - // the control is showing - if (controlOrigin.y < y) { - if (controlSize.y < area.height) - y = controlOrigin.y + controlSize.y - area.height; - else - y = controlOrigin.y; - } - - if (scompOrigin.x != x || scompOrigin.y != y) { - // scroll to reveal - scomp.setOrigin(x, y); - } - } - - public static void ensureVisible(ScrolledComposite scomp, Control control, - MouseEvent e) { - Point controlOrigin = getControlLocation(scomp, control); - int rX = controlOrigin.x + e.x; - int rY = controlOrigin.y + e.y; - Rectangle area = scomp.getClientArea(); - Point scompOrigin = scomp.getOrigin(); - - int x = scompOrigin.x; - int y = scompOrigin.y; - // System.out.println("Ensure: area="+area+", origin="+scompOrigin+", - // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y); - - // horizontal right - if (rX > scompOrigin.x + area.width) { - x = rX - area.width; - } - // horizontal left - else if (rX < x) { - x = rX; - } - // vertical bottom - if (rY > scompOrigin.y + area.height) { - y = rY - area.height; - } - // vertical top - else if (rY < y) { - y = rY; - } - - if (scompOrigin.x != x || scompOrigin.y != y) { - // scroll to reveal - scomp.setOrigin(x, y); - } - } - - public static Point getControlLocation(ScrolledComposite scomp, - Control control) { - int x = 0; - int y = 0; - Control content = scomp.getContent(); - Control currentControl = control; - for (;;) { - if (currentControl == content) - break; - Point location = currentControl.getLocation(); - // if (location.x > 0) - // x += location.x; - // if (location.y > 0) - // y += location.y; - x += location.x; - y += location.y; - currentControl = currentControl.getParent(); - } - return new Point(x, y); - } - - static void scrollVertical(ScrolledComposite scomp, boolean up) { - scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT); - } - - static void scrollHorizontal(ScrolledComposite scomp, boolean left) { - scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0); - } - - static void scrollPage(ScrolledComposite scomp, boolean up) { - Rectangle clientArea = scomp.getClientArea(); - int increment = up ? -clientArea.height : clientArea.height; - scroll(scomp, 0, increment); - } - - static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) { - Point origin = scomp.getOrigin(); - Point contentSize = scomp.getContent().getSize(); - int xorigin = origin.x + xoffset; - int yorigin = origin.y + yoffset; - xorigin = Math.max(xorigin, 0); - xorigin = Math.min(xorigin, contentSize.x - 1); - yorigin = Math.max(yorigin, 0); - yorigin = Math.min(yorigin, contentSize.y - 1); - scomp.setOrigin(xorigin, yorigin); - } - -// RAP [rh] FormUtil#updatePageIncrement: empty implementation - public static void updatePageIncrement(ScrolledComposite scomp) { -// ScrollBar vbar = scomp.getVerticalBar(); -// if (vbar != null) { -// Rectangle clientArea = scomp.getClientArea(); -// int increment = clientArea.height - 5; -// vbar.setPageIncrement(increment); -// } -// ScrollBar hbar = scomp.getHorizontalBar(); -// if (hbar != null) { -// Rectangle clientArea = scomp.getClientArea(); -// int increment = clientArea.width - 5; -// hbar.setPageIncrement(increment); -// } - } - - public static void processKey(int keyCode, Control c) { - if (c.isDisposed()) { - return; - } - ScrolledComposite scomp = FormUtil.getScrolledComposite(c); - if (scomp != null) { - if (c instanceof Combo) - return; - switch (keyCode) { - case SWT.ARROW_DOWN: - if (scomp.getData("novarrows") == null) //$NON-NLS-1$ - FormUtil.scrollVertical(scomp, false); - break; - case SWT.ARROW_UP: - if (scomp.getData("novarrows") == null) //$NON-NLS-1$ - FormUtil.scrollVertical(scomp, true); - break; - case SWT.ARROW_LEFT: - FormUtil.scrollHorizontal(scomp, true); - break; - case SWT.ARROW_RIGHT: - FormUtil.scrollHorizontal(scomp, false); - break; - case SWT.PAGE_UP: - FormUtil.scrollPage(scomp, true); - break; - case SWT.PAGE_DOWN: - FormUtil.scrollPage(scomp, false); - break; - } - } - } - - public static boolean isWrapControl(Control c) { - if ((c.getStyle() & SWT.WRAP) != 0) - return true; - if (c instanceof Composite) { - return false; -// return ((Composite) c).getLayout() instanceof ILayoutExtension; - } - return false; - } - - public static int getWidthHint(int wHint, Control c) { - boolean wrap = isWrapControl(c); - return wrap ? wHint : SWT.DEFAULT; - } - - public static int getHeightHint(int hHint, Control c) { - if (c instanceof Composite) { - Layout layout = ((Composite) c).getLayout(); -// if (layout instanceof ColumnLayout) -// return hHint; - } - return SWT.DEFAULT; - } - - public static int computeMinimumWidth(Control c, boolean changed) { - if (c instanceof Composite) { - Layout layout = ((Composite) c).getLayout(); -// if (layout instanceof ILayoutExtension) -// return ((ILayoutExtension) layout).computeMinimumWidth( -// (Composite) c, changed); - } - return c.computeSize(FormUtil.getWidthHint(5, c), SWT.DEFAULT, changed).x; - } - - public static int computeMaximumWidth(Control c, boolean changed) { - if (c instanceof Composite) { - Layout layout = ((Composite) c).getLayout(); -// if (layout instanceof ILayoutExtension) -// return ((ILayoutExtension) layout).computeMaximumWidth( -// (Composite) c, changed); - } - return c.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x; - } - -// public static Form getForm(Control c) { -// Composite parent = c.getParent(); -// while (parent != null) { -// if (parent instanceof Form) { -// return (Form) parent; -// } -// parent = parent.getParent(); -// } -// return null; -// } - -// RAP [rh] FormUtil#createAlphaMashImage unnecessary -// public static Image createAlphaMashImage(Device device, Image srcImage) { -// Rectangle bounds = srcImage.getBounds(); -// int alpha = 0; -// int calpha = 0; -// ImageData data = srcImage.getImageData(); -// // Create a new image with alpha values alternating -// // between fully transparent (0) and fully opaque (255). -// // This image will show the background through the -// // transparent pixels. -// for (int i = 0; i < bounds.height; i++) { -// // scan line -// alpha = calpha; -// for (int j = 0; j < bounds.width; j++) { -// // column -// data.setAlpha(j, i, alpha); -// alpha = alpha == 255 ? 0 : 255; -// } -// calpha = calpha == 255 ? 0 : 255; -// } -// return new Image(device, data); -// } - - public static boolean mnemonicMatch(String text, char key) { - char mnemonic = findMnemonic(text); - if (mnemonic == '\0') - return false; - return Character.toUpperCase(key) == Character.toUpperCase(mnemonic); - } - - private static char findMnemonic(String string) { - int index = 0; - int length = string.length(); - do { - while (index < length && string.charAt(index) != '&') - index++; - if (++index >= length) - return '\0'; - if (string.charAt(index) != '&') - return string.charAt(index); - index++; - } while (index < length); - return '\0'; - } - - public static void setFocusScrollingEnabled(Control c, boolean enabled) { - ScrolledComposite scomp = null; - - if (c instanceof ScrolledComposite) - scomp = (ScrolledComposite)c; - else - scomp = getScrolledComposite(c); - if (scomp!=null) - scomp.setData(FormUtil.FOCUS_SCROLLING, enabled?null:Boolean.FALSE); - } - - // RAP [rh] FormUtil#setAntialias unnecessary -// public static void setAntialias(GC gc, int style) { -// if (!gc.getAdvanced()) { -// gc.setAdvanced(true); -// if (!gc.getAdvanced()) -// return; -// } -// gc.setAntialias(style); -// } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java deleted file mode 100644 index cf0e5d35e..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -/** - * A place to hold all the color constants used in the forms package. - * - * @since 1.0 - */ - -public interface IFormColors { - /** - * A prefix for all the keys. - */ - String PREFIX = "org.eclipse.ui.forms."; //$NON-NLS-1$ - /** - * Key for the form title foreground color. - */ - String TITLE = PREFIX + "TITLE"; //$NON-NLS-1$ - - /** - * A prefix for the header color constants. - */ - String H_PREFIX = PREFIX + "H_"; //$NON-NLS-1$ - /* - * A prefix for the section title bar color constants. - */ - String TB_PREFIX = PREFIX + "TB_"; //$NON-NLS-1$ - /** - * Key for the form header background gradient ending color. - */ - String H_GRADIENT_END = H_PREFIX + "GRADIENT_END"; //$NON-NLS-1$ - - /** - * Key for the form header background gradient starting color. - * - */ - String H_GRADIENT_START = H_PREFIX + "GRADIENT_START"; //$NON-NLS-1$ - /** - * Key for the form header bottom keyline 1 color. - * - */ - String H_BOTTOM_KEYLINE1 = H_PREFIX + "BOTTOM_KEYLINE1"; //$NON-NLS-1$ - /** - * Key for the form header bottom keyline 2 color. - * - */ - String H_BOTTOM_KEYLINE2 = H_PREFIX + "BOTTOM_KEYLINE2"; //$NON-NLS-1$ - /** - * Key for the form header light hover color. - * - */ - String H_HOVER_LIGHT = H_PREFIX + "H_HOVER_LIGHT"; //$NON-NLS-1$ - /** - * Key for the form header full hover color. - * - */ - String H_HOVER_FULL = H_PREFIX + "H_HOVER_FULL"; //$NON-NLS-1$ - - /** - * Key for the tree/table border color. - */ - String BORDER = PREFIX + "BORDER"; //$NON-NLS-1$ - - /** - * Key for the section separator color. - */ - String SEPARATOR = PREFIX + "SEPARATOR"; //$NON-NLS-1$ - - /** - * Key for the section title bar background. - */ - String TB_BG = TB_PREFIX + "BG"; //$NON-NLS-1$ - - /** - * Key for the section title bar foreground. - */ - String TB_FG = TB_PREFIX + "FG"; //$NON-NLS-1$ - - /** - * Key for the section title bar gradient. - * @deprecated Since 3.3, this color is not used any more. The - * tool bar gradient is created starting from {@link #TB_BG} to - * the section background color. - */ - String TB_GBG = TB_BG; - - /** - * Key for the section title bar border. - */ - String TB_BORDER = TB_PREFIX + "BORDER"; //$NON-NLS-1$ - - /** - * Key for the section toggle color. Since 3.1, this color is used for all - * section styles. - */ - String TB_TOGGLE = TB_PREFIX + "TOGGLE"; //$NON-NLS-1$ - - /** - * Key for the section toggle hover color. - * - */ - String TB_TOGGLE_HOVER = TB_PREFIX + "TOGGLE_HOVER"; //$NON-NLS-1$ -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java deleted file mode 100644 index 954cc0372..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java +++ /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). - *

- * The form part has two 'out of sync' states in respect to the model(s) that - * feed the form: dirty and stale. 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). - *

- * 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 true if the part has selected and revealed the - * input object, false 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). - *

- * 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 true if the part needs refreshing, - * false otherwise. - */ - boolean isStale(); - - /** - * Refreshes the part completely from the information freshly obtained from - * the model. The method will not be called if the part is not stale. - * Otherwise, the part is responsible for clearing the 'stale' flag after - * refreshing itself. - */ - void refresh(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java deleted file mode 100644 index 490d3a303..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java +++ /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. - *

- * 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. - *

- * 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 true, 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 true if the form contains this object, - * false otherwise. - */ - boolean setInput(Object input); - - /** - * Returns the current page input. - * - * @return page input object or null if not applicable. - */ - Object getInput(); - - /** - * Tests if form is dirty. A managed form is dirty if at least one managed - * part is dirty. - * - * @return true if at least one managed part is dirty, - * false 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 true if the form is stale, false - * otherwise. - */ - boolean isStale(); - - /** - * Notifies the form that the stale state of one of its parts has changed. - * The global stale state of the form can be obtained by calling 'isStale'. - */ - void staleStateChanged(); - - /** - * Refreshes the form by refreshing every part that is stale. - */ - void refresh(); - - /** - * Sets the container that owns this form. Depending on the context, the - * container may be wizard, editor page, editor etc. - * - * @param container - * the container of this form - */ - void setContainer(Object container); - - /** - * Returns the container of this form. - * - * @return the form container - */ - Object getContainer(); - - /** - * Returns the message manager that will keep track of messages in this - * form. - * - * @return the message manager instance - */ -// IMessageManager getMessageManager(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java deleted file mode 100644 index 0f557d41f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import org.eclipse.jface.viewers.ISelection; - -/** - * Form parts can implement this interface if they want to be - * notified when another part on the same form changes selection - * state. - * - * @see IFormPart - * @since 1.0 - */ -public interface IPartSelectionListener { - /** - * Called when the provided part has changed selection state. - * - * @param part - * the selection source - * @param selection - * the new selection - */ - public void selectionChanged(IFormPart part, ISelection selection); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java deleted file mode 100644 index 4140465a1..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java +++ /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. - *

- * 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. - *

- * 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 Display.syncExec or - * asyncExec. - */ - public void refresh() { - Thread t = Thread.currentThread(); - Thread dt = toolkit.getColors().getDisplay().getThread(); - if (t.equals(dt)) - doRefresh(); - else { - toolkit.getColors().getDisplay().asyncExec(new Runnable() { - public void run() { - doRefresh(); - } - }); - } - } - - private void doRefresh() { - int nrefreshed = 0; - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isStale()) { - part.refresh(); - nrefreshed++; - } - } -// if (nrefreshed > 0) -// form.reflow(true); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#commit(boolean) - */ - public void commit(boolean onSave) { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isDirty()) - part.commit(onSave); - } - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#setInput(java.lang.Object) - */ - public boolean setInput(Object input) { - boolean pageResult = false; - - this.input = input; - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - boolean result = part.setFormInput(input); - if (result) - pageResult = true; - } - return pageResult; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getInput() - */ - public Object getInput() { - return input; - } - - /** - * Transfers the focus to the first form part. - */ - public void setFocus() { - if (parts.size() > 0) { - IFormPart part = (IFormPart) parts.get(0); - part.setFocus(); - } - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#isDirty() - */ - public boolean isDirty() { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isDirty()) - return true; - } - return false; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#isStale() - */ - public boolean isStale() { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isStale()) - return true; - } - return false; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#dirtyStateChanged() - */ - public void dirtyStateChanged() { - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#staleStateChanged() - */ - public void staleStateChanged() { - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getContainer() - */ - public Object getContainer() { - return container; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#setContainer(java.lang.Object) - */ - public void setContainer(Object container) { - this.container = container; - } - - /* (non-Javadoc) - * @see org.eclipse.ui.forms.IManagedForm#getMessageManager() - */ -// public IMessageManager getMessageManager() { -// return form.getMessageManager(); -// } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java deleted file mode 100644 index 7fa00d9c2..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java +++ /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. - *

- * 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. - *

- * Subclasses should extend this class and implement addPages - * method. One of the two addPage 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 - * IFormPage interface. These pages will be created lazily and - * they will share the common key binding and selection service. Since 3.1, - * FormEditor is a page change provider. It allows listeners to attach to it and - * get notified when pages are changed. This new API in JFace allows dynamic - * help to update on page changes. - * - * @since 1.0 - */ -// RAP [if] As RAP is still using workbench 3.4, the implementation of -// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code -// with the adoption of workbench > 3.5 -//public abstract class FormEditor extends MultiPageEditorPart { -public abstract class FormEditor implements - IPageChangeProvider { - private FormToolkit formToolkit; - - -public FormToolkit getToolkit() { - return formToolkit; - } - -public void editorDirtyStateChanged() { - -} - -public FormPage getActivePageInstance() { - return null; -} - - // RAP [if] As RAP is still using workbench 3.4, the implementation of -// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code -// with the adoption of workbench > 3.5 - private ListenerList pageListeners = new ListenerList(); - - /* - * (non-Javadoc) - * - * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener) - */ - public void addPageChangedListener(IPageChangedListener listener) { - pageListeners.add(listener); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener) - */ - public void removePageChangedListener(IPageChangedListener listener) { - pageListeners.remove(listener); - } - - private void firePageChanged(final PageChangedEvent event) { - Object[] listeners = pageListeners.getListeners(); - for (int i = 0; i < listeners.length; ++i) { - final IPageChangedListener l = (IPageChangedListener) listeners[i]; - SafeRunnable.run(new SafeRunnable() { - public void run() { - l.pageChanged(event); - } - }); - } - } -// RAPEND [if] -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java deleted file mode 100644 index 1511cf38c..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java +++ /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 - * initialize 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 true if the page is currently active, - * false 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 - * createFormContent(IManagedForm) 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 null- form page has no title image. Subclasses - * may override. - * - * @return null - */ - 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 true if the managed form is dirty, - * false 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 false - */ - 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 true if the page has been successfully selected - * and revealed by one of the managed form parts, false - * 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 true - */ - public boolean canLeaveThePage() { - return true; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java deleted file mode 100644 index eb08cb59d..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java +++ /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: - *

    - *
  • The form page has a managed form
  • - *
  • The form page has a unique id
  • - *
  • The form page can be GUI but can also wrap a complete - * editor class (in that case, it should return true - * from isEditor() method).
  • - *
  • The form page is lazy i.e. understands that - * its part control will be created at the last possible - * moment.
  • . - *
- *

Existing editors can be wrapped by implementing - * this interface. In this case, 'isEditor' should return true. - * A common editor to wrap in TextEditor 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 null 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 - * false) or lazily create and/or populate the content on - * true. - * - * @param active - * true if page should be visible, false - * otherwise. - */ - void setActive(boolean active); - /** - * Returns true if page is currently active, false if not. - * - * @return true 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 true if the editor can flip to another page, - * false otherwise. - */ - boolean canLeaveThePage(); - /** - * Returns the control associated with this page. - * - * @return the control of this page if created or null 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 true if the page wraps an editor, - * false 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, false should be returned to allow another - * page to try. - * - * @param object - * object to select and reveal - * @return true if the request was successful, false - * otherwise. - */ - boolean selectReveal(Object object); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java deleted file mode 100644 index e74de5ee8..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.argeo.cms.ui.forms; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable String that displays a browsable link when read-only */ -public class EditableLink extends EditablePropertyString implements - EditablePart { - private static final long serialVersionUID = 5055000749992803591L; - - private String type; - private String message; - private boolean readOnly; - - public EditableLink(Composite parent, int style, Node node, - String propertyName, String type, String message) - throws RepositoryException { - super(parent, style, node, propertyName, message); - this.message = message; - this.type = type; - - readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - if (node.hasProperty(propertyName)) { - this.setStyle(FormStyle.propertyText.style()); - this.setText(node.getProperty(propertyName).getString()); - } else { - this.setStyle(FormStyle.propertyMessage.style()); - this.setText(""); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message); - else if (readOnly) - setLinkValue(lbl, text); - else - // if canEdit() we put only the value with no link - // to avoid glitches of the edition life cycle - lbl.setText(text); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - txt.setMessage(message); - } else - txt.setText(text); - } - } - - private void setLinkValue(Label lbl, String text) { - if (FormStyle.email.style().equals(type)) - lbl.setText(FormUtils.getMailLink(text)); - else if (FormStyle.phone.style().equals(type)) - lbl.setText(FormUtils.getPhoneLink(text)); - else if (FormStyle.website.style().equals(type)) - lbl.setText(FormUtils.getUrlLink(text)); - else if (FormStyle.facebook.style().equals(type) - || FormStyle.instagram.style().equals(type) - || FormStyle.linkedIn.style().equals(type) - || FormStyle.twitter.style().equals(type)) - lbl.setText(FormUtils.getUrlLink(text)); - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java deleted file mode 100644 index fd3f48e3a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Display, add or remove values from a list in a CMS context */ -public class EditableMultiStringProperty extends StyledControl implements EditablePart { - private static final long serialVersionUID = -7044614381252178595L; - - private String propertyName; - private String message; - // TODO implement the ability to provide a list of possible values -// private String[] possibleValues; - private boolean canEdit; - private SelectionListener removeValueSL; - private List 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 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 getValues() { - return values; - } - - public void setValues(List values) { - this.values = values; - } - - // Row layout items do not need explicit layout data - protected void setControlLayoutData(Control control) { - } - - /** To be overridden */ - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsSwtUtils.fillWidth()); - } - - @Override - public Control getControl() { - return super.getControl(); - } - - @Override - protected Control createControl(Composite box, String style) { - Composite row = new Composite(box, SWT.NO_FOCUS); - row.setLayoutData(EclipseUiUtils.fillAll()); - - RowLayout rl = new RowLayout(SWT.HORIZONTAL); - rl.wrap = true; - rl.spacing = rowSpacing; - rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging; - row.setLayout(rl); - - if (values != null) { - for (final String value : values) { - if (canEdit) - createRemovableValue(row, SWT.SINGLE, value); - else - createValueLabel(row, SWT.SINGLE, value); - } - } - - if (!canEdit) - return row; - else if (isEditing()) - return createText(row, style); - else - return createLabel(row, style); - } - - /** - * Override to provide specific layout for the existing values, typically adding - * a pound (#) char for tags or anchor info for browsable links. We assume the - * parent composite already has a layout and it is the caller responsibility to - * apply corresponding layout data - */ - protected Label createValueLabel(Composite parent, int style, String value) { - Label label = new Label(parent, style); - label.setText("#" + value); - CmsSwtUtils.markup(label); - CmsSwtUtils.style(label, FormStyle.propertyText.style()); - return label; - } - - private Composite createRemovableValue(Composite parent, int style, String value) { - Composite valCmp = new Composite(parent, SWT.NO_FOCUS); - GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)); - gl.marginRight = oneValueMargingRight; - valCmp.setLayout(gl); - - createValueLabel(valCmp, SWT.WRAP, value); - - Button deleteBtn = new Button(valCmp, SWT.FLAT); - deleteBtn.setData(FormConstants.LINKED_VALUE, value); - deleteBtn.addSelectionListener(removeValueSL); - CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX); - GridData gd = new GridData(); - gd.heightHint = btnHeight; - gd.widthHint = btnWidth; - gd.horizontalIndent = btnHorizontalIndent; - deleteBtn.setLayoutData(gd); - - return valCmp; - } - - protected Text createText(Composite box, String style) { - final Text text = new Text(box, getStyle()); - // The "add new value" text is not meant to change, so we can set it on - // creation - text.setMessage(message); - CmsSwtUtils.style(text, style); - text.setFocus(); - - text.addTraverseListener(new TraverseListener() { - private static final long serialVersionUID = 1L; - - public void keyTraversed(TraverseEvent e) { - if (e.keyCode == SWT.CR) { - addValue(text); - e.doit = false; - } - } - }); - - // The OK button does not work with the focusOut listener - // because focus out is called before the OK button is pressed - - // // we must call layout() now so that the row data can compute the - // height - // // of the other controls. - // text.getParent().layout(); - // int height = text.getSize().y; - // - // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM); - // okBtn.setText("OK"); - // RowData rd = new RowData(SWT.DEFAULT, height - 2); - // okBtn.setLayoutData(rd); - // - // okBtn.addSelectionListener(new SelectionAdapter() { - // private static final long serialVersionUID = 2780819012423622369L; - // - // @Override - // public void widgetSelected(SelectionEvent e) { - // addValue(text); - // } - // }); - - return text; - } - - /** Performs the real addition, overwrite to make further sanity checks */ - protected void addValue(Text text) { - String value = text.getText(); - String errMsg = null; - - if (EclipseUiUtils.isEmpty(value)) - return; - - if (values.contains(value)) - errMsg = "Dupplicated value: " + value + ", please correct and try again"; - if (errMsg != null) - MessageDialog.openError(this.getShell(), "Addition not allowed", errMsg); - else { - values.add(value); - Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value); - newCmp.moveAbove(text); - text.setText(""); - newCmp.getParent().layout(); - } - } - - protected Label createLabel(Composite box, String style) { - if (canEdit) { - Label lbl = new Label(box, getStyle()); - lbl.setText(message); - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - return null; - } - - protected void clear(boolean deep) { - Control child = getControl(); - if (deep) - super.clear(deep); - else { - child.getParent().dispose(); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (canEdit) - lbl.setText(text); - else - lbl.setText(""); - } else if (child instanceof Text) { - Text txt = (Text) child; - txt.setText(text); - } - } - - public synchronized void startEditing() { - CmsSwtUtils.style(getControl(), FormStyle.propertyText.style()); -// getControl().setData(STYLE, FormStyle.propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style()); -// getControl().setData(STYLE, FormStyle.propertyMessage.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java deleted file mode 100644 index 8591a925f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java +++ /dev/null @@ -1,298 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.DateTime; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** CMS form part to display and edit a date */ -public class EditablePropertyDate extends StyledControl implements EditablePart { - private static final long serialVersionUID = 2500215515778162468L; - - // Context - private String propertyName; - private String message; - private DateFormat dateFormat; - - // UI Objects - private Text dateTxt; - private Button openCalBtn; - - // TODO manage within the CSS - private int fieldBtnSpacing = 5; - - /** - * - * @param parent - * @param style - * @param node - * @param propertyName - * @param message - * @param dateFormat provide a {@link DateFormat} as contract to be able to - * read/write dates as strings - * @throws RepositoryException - */ - public EditablePropertyDate(Composite parent, int style, Node node, String propertyName, String message, - DateFormat dateFormat) throws RepositoryException { - super(parent, style, node, false); - - this.propertyName = propertyName; - this.message = message; - this.dateFormat = dateFormat; - - if (node.hasProperty(propertyName)) { - this.setStyle(FormStyle.propertyText.style()); - this.setText(dateFormat.format(node.getProperty(propertyName).getDate().getTime())); - } else { - this.setStyle(FormStyle.propertyMessage.style()); - this.setText(message); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message); - else - lbl.setText(text); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - } else - txt.setText(text); - } - } - - public synchronized void startEditing() { - // if (dateTxt != null && !dateTxt.isDisposed()) - CmsSwtUtils.style(getControl(), FormStyle.propertyText); -// getControl().setData(STYLE, FormStyle.propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - if (EclipseUiUtils.isEmpty(dateTxt.getText())) - CmsSwtUtils.style(getControl(), FormStyle.propertyMessage); -// getControl().setData(STYLE, FormStyle.propertyMessage.style()); - else - CmsSwtUtils.style(getControl(), FormStyle.propertyText); -// getControl().setData(STYLE, FormStyle.propertyText.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - return createCustomEditableControl(box, style); - } else - return createLabel(box, style); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle() | SWT.WRAP); - lbl.setLayoutData(CmsSwtUtils.fillWidth()); - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - private Control createCustomEditableControl(Composite box, String style) { - box.setLayoutData(CmsSwtUtils.fillWidth()); - Composite dateComposite = new Composite(box, SWT.NONE); - GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)); - gl.horizontalSpacing = fieldBtnSpacing; - dateComposite.setLayout(gl); - dateTxt = new Text(dateComposite, SWT.BORDER); - CmsSwtUtils.style(dateTxt, style); - dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT)); - dateTxt.setToolTipText( - "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar"); - openCalBtn = new Button(dateComposite, SWT.FLAT); - CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX); - GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false); - gd.heightHint = 17; - openCalBtn.setLayoutData(gd); - // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN); - - openCalBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 1L; - - public void widgetSelected(SelectionEvent event) { - CalendarPopup popup = new CalendarPopup(dateTxt); - popup.open(); - } - }); - - // dateTxt.addFocusListener(new FocusListener() { - // private static final long serialVersionUID = 1L; - // - // @Override - // public void focusLost(FocusEvent event) { - // String newVal = dateTxt.getText(); - // // Enable reset of the field - // if (FormUtils.notNull(newVal)) - // calendar = null; - // else { - // try { - // Calendar newCal = parseDate(newVal); - // // DateText.this.setText(newCal); - // calendar = newCal; - // } catch (ParseException pe) { - // // Silent. Manage error popup? - // if (calendar != null) - // EditablePropertyDate.this.setText(calendar); - // } - // } - // } - // - // @Override - // public void focusGained(FocusEvent event) { - // } - // }); - return dateTxt; - } - - protected void clear(boolean deep) { - Control child = getControl(); - if (deep || child instanceof Label) - super.clear(deep); - else { - child.getParent().dispose(); - } - } - - /** Enable setting a custom tooltip on the underlying text */ - @Deprecated - public void setToolTipText(String toolTipText) { - dateTxt.setToolTipText(toolTipText); - } - - @Deprecated - /** Enable setting a custom message on the underlying text */ - public void setMessage(String message) { - dateTxt.setMessage(message); - } - - @Deprecated - public void setText(Calendar cal) { - String newValueStr = ""; - if (cal != null) - newValueStr = dateFormat.format(cal.getTime()); - if (!newValueStr.equals(dateTxt.getText())) - dateTxt.setText(newValueStr); - } - - // UTILITIES TO MANAGE THE CALENDAR POPUP - // TODO manage the popup shell in a cleaner way - private class CalendarPopup extends Shell { - private static final long serialVersionUID = 1L; - private DateTime dateTimeCtl; - - public CalendarPopup(Control source) { - super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - populate(); - // Add border and shadow style - CmsSwtUtils.markup(CalendarPopup.this); - CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style()); - pack(); - layout(); - setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3)); - - addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - close(); - dispose(); - } - }); - open(); - } - - private void setProperty() { - // Direct set does not seems to work. investigate - // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(), - // dateTimeCtl.getDay(), 12, 0); - Calendar cal = new GregorianCalendar(); - cal.set(Calendar.YEAR, dateTimeCtl.getYear()); - cal.set(Calendar.MONTH, dateTimeCtl.getMonth()); - cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay()); - String dateStr = dateFormat.format(cal.getTime()); - dateTxt.setText(dateStr); - } - - protected void populate() { - setLayout(EclipseUiUtils.noSpaceGridLayout()); - - dateTimeCtl = new DateTime(this, SWT.CALENDAR); - dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll()); - - Calendar calendar = FormUtils.parseDate(dateFormat, dateTxt.getText()); - - if (calendar != null) - dateTimeCtl.setDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH)); - - dateTimeCtl.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -8414377364434281112L; - - @Override - public void widgetSelected(SelectionEvent e) { - setProperty(); - } - }); - - dateTimeCtl.addMouseListener(new MouseListener() { - private static final long serialVersionUID = 1L; - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void mouseDown(MouseEvent e) { - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - setProperty(); - close(); - dispose(); - } - }); - } - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java deleted file mode 100644 index 092009355..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.cms.ui.forms; - -import static org.argeo.cms.ui.forms.FormStyle.propertyMessage; -import static org.argeo.cms.ui.forms.FormStyle.propertyText; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.widgets.EditableText; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable String in a CMS context */ -public class EditablePropertyString extends EditableText implements EditablePart { - private static final long serialVersionUID = 5055000749992803591L; - - private String propertyName; - private String message; - - // encode the '&' character in rap - private final static String AMPERSAND = "&"; - 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("
", "\n")); - } - } - - public synchronized void startEditing() { - CmsSwtUtils.style(getControl(), FormStyle.propertyText); - super.startEditing(); - } - - public synchronized void stopEditing() { - if (EclipseUiUtils.isEmpty(((Text) getControl()).getText())) - CmsSwtUtils.style(getControl(), FormStyle.propertyMessage); - else - CmsSwtUtils.style(getControl(), FormStyle.propertyText); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java deleted file mode 100644 index fe9f7e7d7..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.cms.ui.forms; - -/** Constants used in the various CMS Forms */ -public interface FormConstants { - // DATAKEYS - public final static String LINKED_VALUE = "LinkedValue"; -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java deleted file mode 100644 index f3a56f7b9..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.util.Observable; -import java.util.Observer; - -import javax.jcr.Node; - -import org.argeo.api.cms.CmsEditable; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; - -/** Add life cycle management abilities to an editable form page */ -public class FormEditorHeader implements SelectionListener, Observer { - private static final long serialVersionUID = 7392898696542484282L; - - // private final Node context; - private final CmsEditable cmsEditable; - private Button publishBtn; - - // Should we provide here the ability to switch from read only to edition - // mode? - // private Button editBtn; - // private boolean readOnly; - - // TODO add information about the current node status, typically if it is - // dirty or not - - private Composite parent; - private Composite display; - private Object layoutData; - - public FormEditorHeader(Composite parent, int style, Node context, - CmsEditable cmsEditable) { - this.cmsEditable = cmsEditable; - this.parent = parent; - // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - // this.context = context; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - refresh(); - } - - public void setLayoutData(Object layoutData) { - this.layoutData = layoutData; - if (display != null && !display.isDisposed()) - display.setLayoutData(layoutData); - } - - protected void refresh() { - if (display != null && !display.isDisposed()) - display.dispose(); - - display = new Composite(parent, SWT.NONE); - display.setLayoutData(layoutData); - - CmsSwtUtils.style(display, FormStyle.header.style()); - display.setBackgroundMode(SWT.INHERIT_FORCE); - - display.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - publishBtn = createSimpleBtn(display, getPublishButtonLabel()); - display.moveAbove(null); - parent.layout(); - } - - private Button createSimpleBtn(Composite parent, String label) { - Button button = new Button(parent, SWT.FLAT | SWT.PUSH); - button.setText(label); - CmsSwtUtils.style(button, FormStyle.header.style()); - button.addSelectionListener(this); - return button; - } - - private String getPublishButtonLabel() { - // Rather check if the current node differs from what has been - // previously committed - // For the time being, we always reach here, the underlying CmsEditable - // is always editing. - if (cmsEditable.isEditing()) - return " Publish "; - else - return " Edit "; - } - - @Override - public void widgetSelected(SelectionEvent e) { - if (e.getSource() == publishBtn) { - // For the time being, the underlying CmsEditable - // is always editing when we reach this point - if (cmsEditable.isEditing()) { - // we always leave the node in a check outed state - cmsEditable.stopEditing(); - cmsEditable.startEditing(); - } else { - cmsEditable.startEditing(); - } - } - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) { - refresh(); - } - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java deleted file mode 100644 index cc732d49d..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java +++ /dev/null @@ -1,608 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.io.IOException; -import java.io.InputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.ValueFormatException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsEditable; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.AbstractPageViewer; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.viewers.Section; -import org.argeo.cms.ui.viewers.SectionPart; -import org.argeo.cms.ui.widgets.EditableImage; -import org.argeo.cms.ui.widgets.Img; -import org.argeo.cms.ui.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadEvent; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.service.ServerPushSession; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.FormLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Manage life cycle of a form page that is linked to a given node */ -public class FormPageViewer extends AbstractPageViewer { - private final static CmsLog log = CmsLog.getLog(FormPageViewer.class); - private static final long serialVersionUID = 5277789504209413500L; - - private final Section mainSection; - - // TODO manage within the CSS - private Integer labelColWidth = null; - private int rowLayoutHSpacing = 8; - - // Context cached in the viewer - // The reference to translate from text to calendar and reverse - private DateFormat dateFormat = new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT); - private CmsImageManager 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 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 valStrings = new ArrayList(); - 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 = (CmsImageManager) 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 valueStrings = new ArrayList(); - - 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 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", "
"); - // Do not make the update if validation fails - try { - MarkupValidatorCopy.getInstance().validate(value); - } catch (Exception e) { - log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node - + ", String cannot be validated - " + e.getMessage()); - return; - } - // TODO check if the newly created property is of the correct type, - // otherwise the property will be silently created with a STRING - // property type. - node.setProperty(propName, value); - } catch (ValueFormatException vfe) { - log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + " - " + vfe.getMessage()); - } - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java deleted file mode 100644 index 24067eaaa..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.argeo.cms.ui.forms; - -import org.argeo.api.cms.CmsStyle; - -/** Syles used */ -public enum FormStyle implements CmsStyle { - // Main - form, title, - // main part - header, headerBtn, headerCombo, section, sectionHeader, - // Property fields - propertyLabel, propertyText, propertyMessage, errorMessage, - // Date - popupCalendar, - // Buttons - starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete, - // Contacts - email, address, phone, website, - // Social Media - facebook, twitter, linkedIn, instagram; - - @Override - public String getClassPrefix() { - return "argeo-form"; - } - - // TODO clean button style management - public final static String BUTTON_SUFFIX = "_btn"; -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java deleted file mode 100644 index 1a445bd76..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.text.DateFormat; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.fieldassist.ControlDecoration; -import org.eclipse.jface.fieldassist.FieldDecorationRegistry; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Utilitary methods to ease implementation of CMS forms */ -public class FormUtils { - private final static CmsLog log = CmsLog.getLog(FormUtils.class); - - public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy"; - - /** Best effort to convert a String to a calendar. Fails silently */ - public static Calendar parseDate(DateFormat dateFormat, String calStr) { - Calendar cal = null; - if (EclipseUiUtils.notEmpty(calStr)) { - try { - Date date = dateFormat.parse(calStr); - cal = new GregorianCalendar(); - cal.setTime(date); - } catch (ParseException pe) { - // Silent - log.warn("Unable to parse date: " + calStr + " - msg: " - + pe.getMessage()); - } - } - return cal; - } - - /** Add a double click listener on tables that display a JCR node list */ - public static void addCanonicalDoubleClickListener(final TableViewer v) { - v.addDoubleClickListener(new IDoubleClickListener() { - - @Override - public void doubleClick(DoubleClickEvent event) { - CmsView cmsView = CmsUiUtils.getCmsView(); - Node node = (Node) ((IStructuredSelection) event.getSelection()) - .getFirstElement(); - try { - cmsView.navigateTo(node.getPath()); - } catch (RepositoryException e) { - throw new CmsException("Unable to get path for node " - + node + " before calling navigateTo(path)", e); - } - } - }); - } - - // MANAGE ERROR DECORATION - - public static ControlDecoration addDecoration(final Text text) { - final ControlDecoration dynDecoration = new ControlDecoration(text, - SWT.LEFT); - Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR); - dynDecoration.setImage(icon); - dynDecoration.setMarginWidth(3); - dynDecoration.hide(); - return dynDecoration; - } - - public static void refreshDecoration(Text text, ControlDecoration deco, - boolean isValid, boolean clean) { - if (isValid || clean) { - text.setBackground(null); - deco.hide(); - } else { - text.setBackground(new Color(text.getDisplay(), 250, 200, 150)); - deco.show(); - } - } - - public static Image getDecorationImage(String image) { - FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault(); - return registry.getFieldDecoration(image).getImage(); - } - - public static void addCompulsoryDecoration(Label label) { - final ControlDecoration dynDecoration = new ControlDecoration(label, - SWT.RIGHT | SWT.TOP); - Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED); - dynDecoration.setImage(icon); - dynDecoration.setMarginWidth(3); - } - - // TODO the read only generation of read only links for various contact type - // should be factorised in the cms Utils. - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able phone number - */ - public static String getPhoneLink(String value) { - return getPhoneLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able phone number - * - * @param value - * @param label - * a potentially distinct label - * @return - */ - public static String getPhoneLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - builder.append("").append(label) - .append(""); - 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("").append(label).append(""); - 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("" + label + ""); - return builder.toString(); - } - - private static String AMPERSAND = "&"; - - /** - * Cleans a String by replacing any '&' by its HTML encoding '&#38;' to - * avoid SAXParseException while rendering HTML with RWT - */ - public static String replaceAmpersand(String value) { - value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND); - return value; - } - - // Prevents instantiation - private FormUtils() { - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java deleted file mode 100644 index 3f588d1ea..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java +++ /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 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(""); - markup.append(text); - markup.append(""); - 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(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append("]>"); - return result.toString(); - } - - private static Map createSupportedElementsMap() { - Map result = new HashMap(); - 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 supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName)); - int index = 0; - String attributeName = attributes.getQName(index); - while (attributeName != null) { - if (!supportedAttributes.contains(attributeName)) { - String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text"; - message = MessageFormat.format(message, new Object[] { attributeName, elementName }); - throw new IllegalArgumentException(message); - } - index++; - attributeName = attributes.getQName(index); - } - } - } - - private static void checkMandatoryAttributes(String elementName, Attributes attributes) { - checkIntAttribute(elementName, attributes, "img", "width"); - checkIntAttribute(elementName, attributes, "img", "height"); - } - - private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName, - String checkedAttributeName) { - if (checkedElementName.equals(elementName)) { - String attribute = attributes.getValue(checkedAttributeName); - try { - Integer.parseInt(attribute); - } catch (NumberFormatException exception) { - String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer"; - Object[] arguments = new Object[] { checkedAttributeName, checkedElementName }; - message = MessageFormat.format(message, arguments); - throw new IllegalArgumentException(message); - } - } - } - - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java deleted file mode 100644 index 5f954c1c4..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS forms, based on SWT/JFace. */ -package org.argeo.cms.ui.forms; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java deleted file mode 100644 index 5a5ecdb8b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java +++ /dev/null @@ -1,524 +0,0 @@ -package org.argeo.cms.ui.fs; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.spi.FileSystemProvider; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.Session; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider; -import org.argeo.eclipse.ui.fs.FsTableViewer; -import org.argeo.eclipse.ui.fs.FsUiConstants; -import org.argeo.eclipse.ui.fs.FsUiUtils; -import org.argeo.eclipse.ui.fs.NioFileLabelProvider; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Text; - -/** - * Default CMS browser composite: a sashForm layout with bookmarks at the left - * hand side, a simple table in the middle and an overview at right hand side. - */ -public class CmsFsBrowser extends Composite { - // private final static Log log = LogFactory.getLog(CmsFsBrowser.class); - private static final long serialVersionUID = -40347919096946585L; - - private final FileSystemProvider nodeFileSystemProvider; - private final Node currentBaseContext; - - // UI Parts for the browser - private Composite leftPannelCmp; - private Composite filterCmp; - private Text filterTxt; - private FsTableViewer directoryDisplayViewer; - private Composite rightPannelCmp; - - private FsContextMenu contextMenu; - - // Local context (this composite is state full) - private Path initialPath; - private Path currDisplayedFolder; - private Path currSelected; - - // local variables (to be cleaned) - private int bookmarkColWith = 500; - - /* - * WARNING: unfinalised implementation of the mechanism to retrieve base - * paths - */ - - private final static String NODE_PREFIX = "node://"; - - private String getCurrentHomePath() { - Session session = null; - try { - Repository repo = currentBaseContext.getSession().getRepository(); - session = CurrentUser.tryAs(() -> repo.login()); - String homepath = CmsJcrUtils.getUserHome(session).getPath(); - return homepath; - } catch (Exception e) { - throw new CmsException("Cannot retrieve Current User Home Path", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - protected Path[] getMyFilesPath() { - // return Paths.get(System.getProperty("user.dir")); - String currHomeUriStr = NODE_PREFIX + getCurrentHomePath(); - try { - URI uri = new URI(currHomeUriStr); - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) { - PrivilegedExceptionAction pea = new PrivilegedExceptionAction() { - @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 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 = "Unknown"; - 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 stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) { - Path uniqueChild = null; - boolean moreThanOne = false; - loop: for (Path entry : stream) { - if (uniqueChild == null) { - uniqueChild = entry; - } else { - moreThanOne = true; - break loop; - } - } - if (!moreThanOne) - return uniqueChild; - return null; - } catch (IOException ioe) { - throw new CmsException( - "Unable to determine unique child existence and get it under " + parent + " with filter " + filter, - ioe); - } - } - - private void modifyFilter(boolean fromOutside) { - if (!fromOutside) - if (currDisplayedFolder != null) { - String filter = filterTxt.getText() + "*"; - directoryDisplayViewer.setInput(currDisplayedFolder, filter); - } - } - - private class BookmarksSelChangeListener implements ISelectionChangedListener { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) event.getSelection(); - if (selection.isEmpty()) - return; - else { - Path newSelected = (Path) selection.getFirstElement(); - if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath)) - return; - initialPath = newSelected; - setInput(newSelected); - } - } - } - - // Simplify UI implementation - private void addProperty(Composite parent, String propName, String value) { - Label contextL = new Label(parent, SWT.NONE); - contextL.setText(propName + ": " + value); - } - - private Label appendTitle(Composite parent, String value) { - Label titleLbl = new Label(parent, SWT.NONE); - titleLbl.setText(value); - titleLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 5; - gd.verticalIndent = 5; - titleLbl.setLayoutData(gd); - return titleLbl; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java deleted file mode 100644 index e875b5a3d..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.ui.fs; - -import java.io.IOException; -import java.io.InputStream; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.specific.FileDropAdapter; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.widgets.Control; - -/** Allows a control to receive file drops. */ -public class FileDrop { - private final static CmsLog log = CmsLog.getLog(FileDrop.class); - - public void createDropTarget(Control control) { - FileDropAdapter fileDropAdapter = new FileDropAdapter() { - @Override - protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { - if (log.isDebugEnabled()) - log.debug("Process upload of " + fileName + " (" + contentType + ")"); - processFileUpload(in, fileName, contentType); - } - }; - DropTarget dropTarget = new DropTarget(control, DND.DROP_MOVE | DND.DROP_COPY); - fileDropAdapter.prepareDropTarget(control, dropTarget); - } - - public void handleFileDrop(Control control, DropTargetEvent event) { - } - - /** Executed in UI thread */ - protected void processFileUpload(InputStream in, String fileName, String contentType) throws IOException { - - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java deleted file mode 100644 index c548e2aa0..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java +++ /dev/null @@ -1,383 +0,0 @@ -package org.argeo.cms.ui.fs; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.apache.commons.io.IOUtils; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.dialogs.SingleValue; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Generic popup context menu to manage NIO Path in a Viewer. */ -public class FsContextMenu extends Shell { - private static final long serialVersionUID = -9120261153509855795L; - - private final static CmsLog log = CmsLog.getLog(FsContextMenu.class); - - // Default known actions - public final static String ACTION_ID_CREATE_FOLDER = "createFolder"; - public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder"; - public final static String ACTION_ID_SHARE_FOLDER = "shareFolder"; - public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder"; - public final static String ACTION_ID_DELETE = "delete"; - public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles"; - public final static String ACTION_ID_OPEN = "open"; - - // Local context - private final CmsFsBrowser browser; - // private final Viewer viewer; - private final static String KEY_ACTION_ID = "actionId"; - private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER, - ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE, - ACTION_ID_OPEN }; - private Map actionButtons = new HashMap(); - - 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 iterator = selection.iterator(); - List paths = new ArrayList<>(); - - while (iterator.hasNext()) { - Path path = (Path) iterator.next(); - builder.append(path.getFileName() + ", "); - paths.add(path); - } - String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2) - + ". Are you sure?"; - if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) { - for (Path path : paths) { - try { - // Might have already been deleted if we are in a tree - Files.deleteIfExists(path); - } catch (IOException e) { - throw new CmsException("Cannot delete path " + path, e); - } - } - browser.refresh(); - } - } - - private void createFolder() { - String msg = "Please provide a name."; - String name = SingleValue.ask("Create folder", msg); - // TODO enhance check of name validity - if (EclipseUiUtils.notEmpty(name)) { - try { - Path child = currFolderPath.resolve(name); - if (Files.exists(child)) - throw new CmsException("An item with name " + name + " already exists at " - + currFolderPath.toString() + ", cannot create"); - else - Files.createDirectories(child); - browser.refresh(); - } catch (IOException e) { - throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e); - } - } - } - - private void uploadFiles() { - try { - FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI); - dialog.setText("Choose one or more files to upload"); - - if (EclipseUiUtils.notEmpty(dialog.open())) { - String[] names = dialog.getFileNames(); - // Workaround small differences between RAP and RCP - // 1. returned names are absolute path on RAP and - // relative in RCP - // 2. in RCP we must use getFilterPath that does not - // exists on RAP - Method filterMethod = null; - Path parPath = null; - try { - filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath"); - String filterPath = (String) filterMethod.invoke(dialog); - parPath = Paths.get(filterPath); - } catch (NoSuchMethodException nsme) { // RAP - } - if (names.length == 0) - return; - else { - loop: for (String name : names) { - Path tmpPath = Paths.get(name); - if (parPath != null) - tmpPath = parPath.resolve(tmpPath); - if (Files.exists(tmpPath)) { - URI uri = tmpPath.toUri(); - String uriStr = uri.toString(); - - if (Files.isDirectory(tmpPath)) { - MessageDialog.openError(browser.getShell(), "Unimplemented directory import", - "Upload of directories in the system is not yet implemented"); - continue loop; - } - Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString()); - InputStream in = null; - try { - in = new ByteArrayInputStream(Files.readAllBytes(tmpPath)); - Files.copy(in, targetPath); - Files.delete(tmpPath); - } finally { - IOUtils.closeQuietly(in); - } - if (log.isDebugEnabled()) - log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString()); - } else { - String msg = "Cannot copy tmp file from " + tmpPath.toString(); - if (parPath != null) - msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS"; - MessageDialog.openError(browser.getShell(), "Missing file", msg); - continue loop; - } - } - browser.refresh(); - } - } - } catch (Exception e) { - e.printStackTrace(); - MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath); - } - } - - public void setCurrFolderPath(Path currFolderPath) { - this.currFolderPath = currFolderPath; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java deleted file mode 100644 index 9ae319282..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.ui.fs; - -/** FS Ui specific CSS styles */ -public interface FsStyles { - String BREAD_CRUMB_BTN = "breadCrumb_btn"; - String CONTEXT_MENU_BOX = "contextMenu_box"; - String BUTTON_SUFFIX = "_btn"; -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java deleted file mode 100644 index 6a6c27286..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace file system components. */ -package org.argeo.cms.ui.fs; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java deleted file mode 100644 index e10da3aed..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.ui.internal; - -import org.argeo.api.cms.CmsState; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - -public class Activator implements BundleActivator { - - // avoid dependency to RWT OSGi - private final static String CONTEXT_NAME_PROP = "contextName"; - - private static ServiceTracker nodeState; - - // @Override - public void start(BundleContext bc) throws Exception { - // UI -// bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), -// LangUtils.dico(CONTEXT_NAME_PROP, "system")); -// bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.dico(CONTEXT_NAME_PROP, "user")); - - nodeState = new ServiceTracker<>(bc, CmsState.class, null); - nodeState.open(); - } - - @Override - public void stop(BundleContext context) throws Exception { - if (nodeState != null) { - nodeState.close(); - nodeState = null; - } - } - - public static CmsState getNodeState() { - return nodeState.getService(); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java deleted file mode 100644 index 44885b1ca..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java +++ /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 arr = new ArrayList(); - 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 arr = new ArrayList(); - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) { - arr.add(nit.nextNode()); - } - return arr.toArray(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public Object getParent(Object element) { - try { - Node node = (Node) element; - if (node.getName().equals("")) - return null; - else - return node.getParent(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public boolean hasChildren(Object element) { - try { - Node node = (Node) element; - return node.hasNodes(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java deleted file mode 100644 index c8582f0c1..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.argeo.cms.ui.internal; - -import static javax.jcr.nodetype.NodeType.NT_FILE; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.FilenameUtils; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.cms.ui.widgets.Img; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadReceiver; - -public class JcrFileUploadReceiver extends FileUploadReceiver { - private Img img; - private final Node parentNode; - private final String nodeName; - private final CmsImageManager imageManager; - - /** If nodeName is null, use the uploaded file name */ - public JcrFileUploadReceiver(Img img, Node parentNode, String nodeName, CmsImageManager imageManager) { - super(); - this.img = img; - this.parentNode = parentNode; - this.nodeName = nodeName; - this.imageManager = imageManager; - } - - @Override - public void receive(InputStream stream, FileDetails details) throws IOException { - try { - String fileName = nodeName != null ? nodeName : details.getFileName(); - String contentType = details.getContentType(); - if (isImage(details.getFileName(), contentType)) { - imageManager.uploadImage(img.getNode(),parentNode, fileName, stream, contentType); - return; - } - - Node fileNode; - if (parentNode.hasNode(fileName)) { - fileNode = parentNode.getNode(fileName); - if (!fileNode.isNodeType(NT_FILE)) - fileNode.remove(); - } - fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream); - - if (contentType != null) { - fileNode.addMixin(NodeType.MIX_MIMETYPE); - fileNode.setProperty(Property.JCR_MIMETYPE, contentType); - } - processNewFile(fileNode); - fileNode.getSession().save(); - } catch (RepositoryException e) { - throw new JcrException("Cannot receive " + details, e); - } - } - - protected Boolean isImage(String fileName, String contentType) { - String ext = FilenameUtils.getExtension(fileName); - return ext != null && (ext.equals("png") || ext.equalsIgnoreCase("jpg")); - } - - protected void processNewFile(Node node) { - - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java deleted file mode 100644 index c5c1a01a2..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.argeo.cms.ui.internal; - -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.argeo.cms.ui.widgets.EditableImage; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Text; - -/** NOT working yet. */ -public class SimpleEditableImage extends EditableImage { - private static final long serialVersionUID = -5689145523114022890L; - - private String src; - private Cms2DSize imageSize; - - public SimpleEditableImage(Composite parent, int swtStyle) { - super(parent, swtStyle); - // load(getControl()); - getParent().layout(); - } - - public SimpleEditableImage(Composite parent, int swtStyle, String src, Cms2DSize imageSize) { - super(parent, swtStyle); - this.src = src; - this.imageSize = imageSize; - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - return createText(box, style); - } else { - return createLabel(box, style); - } - } - - protected String createImgTag() throws RepositoryException { - String imgTag; - if (src != null) - imgTag = CmsUiUtils.img(src, imageSize); - else - imgTag = CmsUiUtils.noImg(imageSize != null ? imageSize : NO_IMAGE_SIZE); - return imgTag; - } - - protected Text createText(Composite box, String style) { - Text text = new Text(box, getStyle()); - CmsSwtUtils.style(text, style); - return text; - } - - public String getSrc() { - return src; - } - - public void setSrc(String src) { - this.src = src; - } - - public Cms2DSize getImageSize() { - return imageSize; - } - - public void setImageSize(Cms2DSize imageSize) { - this.imageSize = imageSize; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java deleted file mode 100644 index 380634118..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.Collections; -import java.util.Map; -import java.util.Observable; -import java.util.TreeMap; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; - -public class DefaultRepositoryRegister extends Observable implements RepositoryRegister { - /** Key for a JCR repository alias */ - private final static String CN = CmsConstants.CN; - /** Key for a JCR repository URI */ - // public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri"; - private final static CmsLog log = CmsLog.getLog(DefaultRepositoryRegister.class); - - /** Read only map which will be directly exposed. */ - private Map repositories = Collections.unmodifiableMap(new TreeMap()); - - @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 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 map = new TreeMap(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 map = new TreeMap(repositories); - if (map.remove(alias) == null) { - log.warn("No repository was registered with alias " + alias); - return; - } - repositories = Collections.unmodifiableMap(map); - setChanged(); - notifyObservers(alias); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java deleted file mode 100644 index 0f7ee7735..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java +++ /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 result = new ArrayList(); - 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 tmp = new ArrayList(); - tmp.add(((Version) parentElement).getFrozenNode()); - return tmp.toArray(); - } - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while getting child " - + "node for version element", re); - } - return null; - } - - public Object getParent(Object element) { - try { - // this will not work in a simpleVersionning environment, parent is - // not a node. - if (element instanceof Node - && ((Node) element).isNodeType(NodeType.NT_FROZEN_NODE)) { - Node node = (Node) element; - return node.getParent(); - } else - return null; - } catch (RepositoryException e) { - return null; - } - } - - public boolean hasChildren(Object element) { - try { - if (element instanceof Version) - return true; - else if (element instanceof Node) - return ((Node) element).hasNodes(); - else - return false; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check children of " + element, e); - } - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java deleted file mode 100644 index e4c5873a0..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.argeo.cms.ui.jcr.model.RepositoriesElem; -import org.argeo.cms.ui.jcr.model.RepositoryElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; - -/** Useful methods to manage the JCR Browser */ -public class JcrBrowserUtils { - - public static String getPropertyTypeAsString(Property prop) { - try { - return PropertyType.nameFromValue(prop.getType()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check type for " + prop, e); - } - } - - /** Insure that the UI component is not stale, refresh if needed */ - public static void forceRefreshIfNeeded(TreeParent element) { - Node curNode = null; - - boolean doRefresh = false; - - try { - if (element instanceof SingleJcrNodeElem) { - curNode = ((SingleJcrNodeElem) element).getNode(); - } else if (element instanceof WorkspaceElem) { - curNode = ((WorkspaceElem) element).getRootNode(); - } - - if (curNode != null && element.getChildren().length != curNode.getNodes().getSize()) - doRefresh = true; - else if (element instanceof RepositoryElem) { - RepositoryElem rn = (RepositoryElem) element; - if (rn.isConnected()) { - String[] wkpNames = rn.getAccessibleWorkspaceNames(); - if (element.getChildren().length != wkpNames.length) - doRefresh = true; - } - } else if (element instanceof RepositoriesElem) { - doRefresh = true; - // Always force refresh for RepositoriesElem : the condition - // below does not take remote repository into account and it is - // not trivial to do so. - - // RepositoriesElem rn = (RepositoriesElem) element; - // if (element.getChildren().length != - // rn.getRepositoryRegister() - // .getRepositories().size()) - // doRefresh = true; - } - if (doRefresh) { - element.clearChildren(); - element.getChildren(); - } - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while synchronising the UI with the JCR repository", re); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java deleted file mode 100644 index 1707681b4..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.Node; - -import org.argeo.cms.ui.jcr.model.RepositoryElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TreeViewer; - -/** Centralizes the management of double click on a NodeTreeViewer */ -public class JcrDClickListener implements IDoubleClickListener { - // private final static Log log = LogFactory - // .getLog(GenericNodeDoubleClickListener.class); - - private TreeViewer nodeViewer; - - // private JcrFileProvider jfp; - // private FileHandler fileHandler; - - public JcrDClickListener(TreeViewer nodeViewer) { - this.nodeViewer = nodeViewer; - // jfp = new JcrFileProvider(); - // Commented out. see https://www.argeo.org/bugzilla/show_bug.cgi?id=188 - // fileHandler = null; - // fileHandler = new FileHandler(jfp); - } - - public void doubleClick(DoubleClickEvent event) { - if (event.getSelection() == null || event.getSelection().isEmpty()) - return; - Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement(); - if (obj instanceof RepositoryElem) { - RepositoryElem rpNode = (RepositoryElem) obj; - if (rpNode.isConnected()) { - rpNode.logout(); - } else { - rpNode.login(); - } - nodeViewer.refresh(obj); - } else if (obj instanceof WorkspaceElem) { - WorkspaceElem wn = (WorkspaceElem) obj; - if (wn.isConnected()) - wn.logout(); - else - wn.login(); - nodeViewer.refresh(obj); - } else if (obj instanceof SingleJcrNodeElem) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; - Node node = sjn.getNode(); - openNode(node); - } - } - - protected void openNode(Node node) { - // TODO implement generic behaviour - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java deleted file mode 100644 index d1d1e31ef..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.swt.graphics.Image; - -/** Shared icons. */ -public class JcrImages { - public final static Image NODE = CmsImages.createIcon("node.gif"); - public final static Image FOLDER = CmsImages.createIcon("folder.gif"); - public final static Image FILE = CmsImages.createIcon("file.gif"); - public final static Image BINARY = CmsImages.createIcon("binary.png"); - public final static Image HOME = CmsImages.createIcon("person-logged-in.png"); - public final static Image SORT = CmsImages.createIcon("sort.gif"); - public final static Image REMOVE = CmsImages.createIcon("remove.gif"); - - public final static Image REPOSITORIES = CmsImages.createIcon("repositories.gif"); - public final static Image REPOSITORY_DISCONNECTED = CmsImages.createIcon("repository_disconnected.gif"); - public final static Image REPOSITORY_CONNECTED = CmsImages.createIcon("repository_connected.gif"); - public final static Image REMOTE_DISCONNECTED = CmsImages.createIcon("remote_disconnected.gif"); - public final static Image REMOTE_CONNECTED = CmsImages.createIcon("remote_connected.gif"); - public final static Image WORKSPACE_DISCONNECTED = CmsImages.createIcon("workspace_disconnected.png"); - public final static Image WORKSPACE_CONNECTED = CmsImages.createIcon("workspace_connected.png"); - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java deleted file mode 100644 index cc8479f78..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java +++ /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 children = new ArrayList(); - NodeIterator nit = parentNode.getNodes(); - while (nit.hasNext()) { - Node node = nit.nextNode(); -// if (node.getName().startsWith("rep:") || node.getName().startsWith("jcr:") -// || node.getName().startsWith("nt:")) -// continue nodes; - children.add(node); - } - Node[] arr = children.toArray(new Node[0]); - Arrays.sort(arr, itemComparator); - return arr; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot list children of " + parentNode, e); - } - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java deleted file mode 100644 index 00449df26..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.cms.ui.jcr.model.RepositoriesElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.eclipse.ui.TreeParent; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Implementation of the {@code ITreeContentProvider} to display multiple - * repository environment in a tree like structure - */ -public class NodeContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -4083809398848374403L; - final private RepositoryRegister repositoryRegister; - final private RepositoryFactory repositoryFactory; - - // Current user session on the default workspace of the argeo Node - final private Session userSession; - final private Keyring keyring; - private boolean sortChildren; - - // Reference for cleaning - private SingleJcrNodeElem homeNode = null; - private RepositoriesElem repositoriesNode = null; - - // Utils - private TreeBrowserComparator itemComparator = new TreeBrowserComparator(); - - public NodeContentProvider(Session userSession, Keyring keyring, - RepositoryRegister repositoryRegister, - RepositoryFactory repositoryFactory, Boolean sortChildren) { - this.userSession = userSession; - this.keyring = keyring; - this.repositoryRegister = repositoryRegister; - this.repositoryFactory = repositoryFactory; - this.sortChildren = sortChildren; - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - if (newInput == null)// dispose - return; - - if (userSession != null) { - Node userHome = CmsJcrUtils.getUserHome(userSession); - if (userHome != null) { - // TODO : find a way to dynamically get alias for the node - if (homeNode != null) - homeNode.dispose(); - homeNode = new SingleJcrNodeElem(null, userHome, - userSession.getUserID(), CmsConstants.EGO_REPOSITORY); - } - } - if (repositoryRegister != null) { - if (repositoriesNode != null) - repositoriesNode.dispose(); - repositoriesNode = new RepositoriesElem("Repositories", - repositoryRegister, repositoryFactory, null, userSession, - keyring); - } - } - - /** - * Sends back the first level of the Tree. Independent from inputElement - * that can be null - */ - public Object[] getElements(Object inputElement) { - List objs = new ArrayList(); - 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 { - - public int category(TreeParent element) { - if (element instanceof SingleJcrNodeElem) { - Node node = ((SingleJcrNodeElem) element).getNode(); - try { - if (node.isNodeType(NodeType.NT_FOLDER)) - return 5; - } catch (RepositoryException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return 10; - } - - public int compare(TreeParent o1, TreeParent o2) { - int cat1 = category(o1); - int cat2 = category(o2); - - if (cat1 != cat2) { - return cat1 - cat2; - } - return o1.getName().compareTo(o2.getName()); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java deleted file mode 100644 index a5751c083..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.NamespaceException; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ui.jcr.model.RemoteRepositoryElem; -import org.argeo.cms.ui.jcr.model.RepositoriesElem; -import org.argeo.cms.ui.jcr.model.RepositoryElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** Provides reasonable defaults for know JCR types. */ -public class NodeLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -3662051696443321843L; - - private final static CmsLog log = CmsLog.getLog(NodeLabelProvider.class); - - public String getText(Object element) { - try { - if (element instanceof SingleJcrNodeElem) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; - return getText(sjn.getNode()); - } else if (element instanceof Node) { - return getText((Node) element); - } else - return super.getText(element); - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected JCR error while getting node name."); - } - } - - protected String getText(Node node) throws RepositoryException { - String label = node.getName(); - StringBuffer mixins = new StringBuffer(""); - for (NodeType type : node.getMixinNodeTypes()) - mixins.append(' ').append(type.getName()); - - return label + " [" + node.getPrimaryNodeType().getName() + mixins + "]"; - } - - @Override - public Image getImage(Object element) { - if (element instanceof RemoteRepositoryElem) { - if (((RemoteRepositoryElem) element).isConnected()) - return JcrImages.REMOTE_CONNECTED; - else - return JcrImages.REMOTE_DISCONNECTED; - } else if (element instanceof RepositoryElem) { - if (((RepositoryElem) element).isConnected()) - return JcrImages.REPOSITORY_CONNECTED; - else - return JcrImages.REPOSITORY_DISCONNECTED; - } else if (element instanceof WorkspaceElem) { - if (((WorkspaceElem) element).isConnected()) - return JcrImages.WORKSPACE_CONNECTED; - else - return JcrImages.WORKSPACE_DISCONNECTED; - } else if (element instanceof RepositoriesElem) { - return JcrImages.REPOSITORIES; - } else if (element instanceof SingleJcrNodeElem) { - Node nodeElem = ((SingleJcrNodeElem) element).getNode(); - return getImage(nodeElem); - - // if (element instanceof Node) { - // return getImage((Node) element); - // } else if (element instanceof WrappedNode) { - // return getImage(((WrappedNode) element).getNode()); - // } else if (element instanceof NodesWrapper) { - // return getImage(((NodesWrapper) element).getNode()); - // } - } - // try { - // return super.getImage(); - // } catch (RepositoryException e) { - // return null; - // } - return super.getImage(element); - } - - protected Image getImage(Node node) { - try { - if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)) - return JcrImages.FILE; - else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) - return JcrImages.FOLDER; - else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_RESOURCE)) - return JcrImages.BINARY; - try { - // TODO check workspace type? - if (node.getDepth() == 1 && node.hasProperty(Property.JCR_ID)) - return JcrImages.HOME; - - // optimizes -// if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME)) -// return JcrImages.HOME; - } catch (NamespaceException e) { - // node namespace is not registered in this repo - } - return JcrImages.NODE; - } catch (RepositoryException e) { - log.warn("Error while retrieving type for " + node + " in order to display corresponding image"); - e.printStackTrace(); - return null; - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java deleted file mode 100644 index 444350aeb..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java +++ /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 repositoryTracker; - - public OsgiRepositoryRegister() { - repositoryTracker = new ServiceTracker(bc, Repository.class, null) { - - @Override - public Repository addingService(ServiceReference reference) { - - Repository repository = super.addingService(reference); - Map props = new HashMap<>(); - for (String key : reference.getPropertyKeys()) { - props.put(key, reference.getProperty(key)); - } - register(repository, props); - return repository; - } - - @Override - public void removedService(ServiceReference reference, Repository service) { - Map props = new HashMap<>(); - for (String key : reference.getPropertyKeys()) { - props.put(key, reference.getProperty(key)); - } - unregister(service, props); - super.removedService(reference, service); - } - - }; - } - - public void init() { - repositoryTracker.open(); - } - - public void destroy() { - repositoryTracker.close(); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java deleted file mode 100644 index fd544bbd8..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java +++ /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 props = new TreeSet(itemComparator); - PropertyIterator pit = ((Node) inputElement).getProperties(); - while (pit.hasNext()) - props.add(pit.nextProperty()); - return props.toArray(); - } - return new Object[] {}; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get element for " - + inputElement, e); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java deleted file mode 100644 index 37b90f7ee..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ViewerCell; - -/** Default basic label provider for a given JCR Node's properties */ -public class PropertyLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -5405794508731390147L; - - // To be able to change column order easily - public static final int COLUMN_PROPERTY = 0; - public static final int COLUMN_VALUE = 1; - public static final int COLUMN_TYPE = 2; - public static final int COLUMN_ATTRIBUTES = 3; - - // Utils - protected DateFormat timeFormatter = new SimpleDateFormat(CmsUiConstants.DATE_TIME_FORMAT); - - public void update(ViewerCell cell) { - Object element = cell.getElement(); - cell.setText(getColumnText(element, cell.getColumnIndex())); - } - - public String getColumnText(Object element, int columnIndex) { - try { - if (element instanceof Property) { - Property prop = (Property) element; - if (prop.isMultiple()) { - switch (columnIndex) { - case COLUMN_PROPERTY: - return prop.getName(); - case COLUMN_VALUE: - // Corresponding values are listed on children - return ""; - case COLUMN_TYPE: - return JcrBrowserUtils.getPropertyTypeAsString(prop); - case COLUMN_ATTRIBUTES: - return JcrUtils.getPropertyDefinitionAsString(prop); - } - } else { - switch (columnIndex) { - case COLUMN_PROPERTY: - return prop.getName(); - case COLUMN_VALUE: - return formatValueAsString(prop.getValue()); - case COLUMN_TYPE: - return JcrBrowserUtils.getPropertyTypeAsString(prop); - case COLUMN_ATTRIBUTES: - return JcrUtils.getPropertyDefinitionAsString(prop); - } - } - } else if (element instanceof Value) { - Value val = (Value) element; - switch (columnIndex) { - case COLUMN_PROPERTY: - // Nothing to show - return ""; - case COLUMN_VALUE: - return formatValueAsString(val); - case COLUMN_TYPE: - // listed on the parent - return ""; - case COLUMN_ATTRIBUTES: - // Corresponding attributes are listed on the parent - return ""; - } - } - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot retrieve prop value on " + element, re); - } - return null; - } - - private String formatValueAsString(Value value) { - // TODO enhance this method - try { - String strValue; - - if (value.getType() == PropertyType.BINARY) - strValue = ""; - else if (value.getType() == PropertyType.DATE) - strValue = timeFormatter.format(value.getDate().getTime()); - else - strValue = value.getString(); - return strValue; - } catch (RepositoryException e) { - throw new EclipseUiException("unexpected error while formatting value", e); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java deleted file mode 100644 index 802c75619..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java +++ /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 getRepositories(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java deleted file mode 100644 index 37dfe2b8f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.version.Version; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** - * Simple wrapping of the ColumnLabelProvider class to provide text display in - * order to build a tree for version. The getText() method does not assume that - * {@link Version} extends {@link Node} class to respect JCR 2.0 specification - * - */ -public class VersionLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = 5270739851193688238L; - - public String getText(Object element) { - try { - if (element instanceof Version) { - Version version = (Version) element; - return version.getName(); - } else if (element instanceof Node) { - return ((Node) element).getName(); - } - } catch (RepositoryException re) { - throw new EclipseUiException( - "Unexpected error while getting element name", re); - } - return super.getText(element); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java deleted file mode 100644 index 61654b61a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.Repository; - -import org.argeo.eclipse.ui.TreeParent; - -/** Wrap a MaintainedRepository */ -public class MaintainedRepositoryElem extends RepositoryElem { - - public MaintainedRepositoryElem(String alias, Repository repository, TreeParent parent) { - super(alias, repository, parent); - // if (!(repository instanceof MaintainedRepository)) { - // throw new ArgeoException("Repository " + alias - // + " is not a maintained repository"); - // } - } - - // protected MaintainedRepository getMaintainedRepository() { - // return (MaintainedRepository) getRepository(); - // } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java deleted file mode 100644 index 428e7f1cd..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import java.util.Arrays; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; - -/** Root of a remote repository */ -public class RemoteRepositoryElem extends RepositoryElem { - private final Keyring keyring; - /** - * A session of the logged in user on the default workspace of the node - * repository. - */ - private final Session userSession; - private final String remoteNodePath; - - private final RepositoryFactory repositoryFactory; - private final String uri; - - public RemoteRepositoryElem(String alias, RepositoryFactory repositoryFactory, String uri, TreeParent parent, - Session userSession, Keyring keyring, String remoteNodePath) { - super(alias, null, parent); - this.repositoryFactory = repositoryFactory; - this.uri = uri; - this.keyring = keyring; - this.userSession = userSession; - this.remoteNodePath = remoteNodePath; - } - - @Override - protected Session repositoryLogin(String workspaceName) throws RepositoryException { - Node remoteRepository = userSession.getNode(remoteNodePath); - String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID).getString(); - if (userID.trim().equals("")) { - return getRepository().login(workspaceName); - } else { - String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; - char[] password = keyring.getAsChars(pwdPath); - try { - SimpleCredentials credentials = new SimpleCredentials(userID, password); - return getRepository().login(credentials, workspaceName); - } finally { - Arrays.fill(password, 0, password.length, ' '); - } - } - } - - @Override - public Repository getRepository() { - if (repository == null) - repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, uri); - return super.getRepository(); - } - - public void remove() { - try { - Node remoteNode = userSession.getNode(remoteNodePath); - remoteNode.remove(); - remoteNode.getSession().save(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot remove " + remoteNodePath, e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java deleted file mode 100644 index 858633202..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.cms.ui.jcr.RepositoryRegister; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; - -/** - * UI Tree component that implements the Argeo abstraction of a - * {@link RepositoryFactory} that enable a user to "mount" various repositories - * in a single Tree like View. It is usually meant to be at the root of the UI - * Tree and thus {@link getParent()} method will return null. - * - * The {@link RepositoryFactory} is injected at instantiation time and must be - * use get or register new {@link Repository} objects upon which a reference is - * kept here. - */ - -public class RepositoriesElem extends TreeParent implements ArgeoNames { - private final RepositoryRegister repositoryRegister; - private final RepositoryFactory repositoryFactory; - - /** - * A session of the logged in user on the default workspace of the node - * repository. - */ - private final Session userSession; - private final Keyring keyring; - - public RepositoriesElem(String name, RepositoryRegister repositoryRegister, RepositoryFactory repositoryFactory, - TreeParent parent, Session userSession, Keyring keyring) { - super(name); - this.repositoryRegister = repositoryRegister; - this.repositoryFactory = repositoryFactory; - this.userSession = userSession; - this.keyring = keyring; - } - - /** - * Override normal behavior to initialize the various repositories only at - * request time - */ - @Override - public synchronized Object[] getChildren() { - if (isLoaded()) { - return super.getChildren(); - } else { - // initialize current object - Map refRepos = repositoryRegister.getRepositories(); - for (String name : refRepos.keySet()) { - Repository repository = refRepos.get(name); - // if (repository instanceof MaintainedRepository) - // super.addChild(new MaintainedRepositoryElem(name, - // repository, this)); - // else - super.addChild(new RepositoryElem(name, repository, this)); - } - - // remote - if (keyring != null) { - try { - addRemoteRepositories(keyring); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot browse remote repositories", e); - } - } - return super.getChildren(); - } - } - - protected void addRemoteRepositories(Keyring jcrKeyring) throws RepositoryException { - Node userHome = CmsJcrUtils.getUserHome(userSession); - if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) { - NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes(); - while (it.hasNext()) { - Node remoteNode = it.nextNode(); - String uri = remoteNode.getProperty(ARGEO_URI).getString(); - try { - RemoteRepositoryElem remoteRepositoryNode = new RemoteRepositoryElem(remoteNode.getName(), - repositoryFactory, uri, this, userSession, jcrKeyring, remoteNode.getPath()); - super.addChild(remoteRepositoryNode); - } catch (Exception e) { - ErrorFeedback.show("Cannot add remote repository " + remoteNode, e); - } - } - } - } - - public void registerNewRepository(String alias, Repository repository) { - // TODO: implement this - // Create a new RepositoryNode Object - // add it - // super.addChild(new RepositoriesNode(...)); - } - - /** Returns the {@link RepositoryRegister} wrapped by this object. */ - public RepositoryRegister getRepositoryRegister() { - return repositoryRegister; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java deleted file mode 100644 index afff3ef9e..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.jcr.JcrUtils; - -/** - * UI Tree component that wraps a JCR {@link Repository}. It also keeps a - * reference to its parent Tree Ui component; typically the unique - * {@link RepositoriesElem} object of the current view to enable bi-directionnal - * browsing in the tree. - */ - -public class RepositoryElem extends TreeParent { - private String alias; - protected Repository repository; - private Session defaultSession = null; - - /** Create a new repository with distinct name and alias */ - public RepositoryElem(String alias, Repository repository, TreeParent parent) { - super(alias); - this.repository = repository; - setParent(parent); - this.alias = alias; - } - - public void login() { - try { - defaultSession = repositoryLogin(CmsConstants.SYS_WORKSPACE); - String[] wkpNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames(); - for (String wkpName : wkpNames) { - if (wkpName.equals(defaultSession.getWorkspace().getName())) - addChild(new WorkspaceElem(this, wkpName, defaultSession)); - else - addChild(new WorkspaceElem(this, wkpName)); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot connect to repository " + alias, e); - } - } - - public synchronized void logout() { - for (Object child : getChildren()) { - if (child instanceof WorkspaceElem) - ((WorkspaceElem) child).logout(); - } - clearChildren(); - JcrUtils.logoutQuietly(defaultSession); - defaultSession = null; - } - - /** - * Actual call to the {@link Repository#login(javax.jcr.Credentials, String)} - * method. To be overridden. - */ - protected Session repositoryLogin(String workspaceName) throws RepositoryException { - return repository.login(workspaceName); - } - - public String[] getAccessibleWorkspaceNames() { - try { - return defaultSession.getWorkspace().getAccessibleWorkspaceNames(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot retrieve workspace names", e); - } - } - - public void createWorkspace(String workspaceName) { - if (!isConnected()) - login(); - try { - defaultSession.getWorkspace().createWorkspace(workspaceName); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot create workspace", e); - } - } - - /** returns the {@link Repository} referenced by the current UI Node */ - public Repository getRepository() { - return repository; - } - - public String getAlias() { - return alias; - } - - public Boolean isConnected() { - if (defaultSession != null && defaultSession.isLive()) - return true; - else - return false; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java deleted file mode 100644 index 859deee97..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Workspace; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; - -/** - * UI Tree component. Wraps a node of a JCR {@link Workspace}. It also keeps a - * reference to its parent node that can either be a {@link WorkspaceElem}, a - * {@link SingleJcrNodeElem} or null if the node is "mounted" as the root of the - * UI tree. - */ -public class SingleJcrNodeElem extends TreeParent { - - private final Node node; - private String alias = null; - - /** Creates a new UiNode in the UI Tree */ - public SingleJcrNodeElem(TreeParent parent, Node node, String name) { - super(name); - setParent(parent); - this.node = node; - } - - /** - * Creates a new UiNode in the UI Tree, keeping a reference to the alias of - * the corresponding repository in the current UI environment. It is useful - * to be able to mount nodes as roots of the UI tree. - */ - public SingleJcrNodeElem(TreeParent parent, Node node, String name, String alias) { - super(name); - setParent(parent); - this.node = node; - this.alias = alias; - } - - /** Returns the node wrapped by the current UI object */ - public Node getNode() { - return node; - } - - protected String getRepositoryAlias() { - return alias; - } - - /** - * Overrides normal behaviour to initialise children only when first - * requested - */ - @Override - public synchronized Object[] getChildren() { - if (isLoaded()) { - return super.getChildren(); - } else { - // initialize current object - try { - NodeIterator ni = node.getNodes(); - while (ni.hasNext()) { - Node curNode = ni.nextNode(); - addChild(new SingleJcrNodeElem(this, curNode, curNode.getName())); - } - return super.getChildren(); - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot initialize SingleJcrNode children", re); - } - } - } - - @Override - public boolean hasChildren() { - try { - if (node.getSession().isLive()) - return node.hasNodes(); - else - return false; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot check children node existence", re); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java deleted file mode 100644 index 24fc5758d..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.AccessDeniedException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -// import javax.jcr.Workspace; -import javax.jcr.Workspace; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.jcr.JcrUtils; - -/** - * UI Tree component. Wraps the root node of a JCR {@link Workspace}. It also - * keeps a reference to its parent {@link RepositoryElem}, to be able to - * retrieve alias of the current used repository - */ -public class WorkspaceElem extends TreeParent { - private Session session = null; - - public WorkspaceElem(RepositoryElem parent, String name) { - this(parent, name, null); - } - - public WorkspaceElem(RepositoryElem parent, String name, Session session) { - super(name); - this.session = session; - setParent(parent); - } - - public synchronized Session getSession() { - return session; - } - - public synchronized Node getRootNode() { - try { - if (session != null) - return session.getRootNode(); - else - return null; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get root node of workspace " + getName(), e); - } - } - - public synchronized void login() { - try { - session = ((RepositoryElem) getParent()).repositoryLogin(getName()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot connect to repository " + getName(), e); - } - } - - public Boolean isConnected() { - if (session != null && session.isLive()) - return true; - else - return false; - } - - @Override - public synchronized void dispose() { - logout(); - super.dispose(); - } - - /** Logouts the session, does not nothing if there is no live session. */ - public synchronized void logout() { - clearChildren(); - JcrUtils.logoutQuietly(session); - session = null; - } - - @Override - public synchronized boolean hasChildren() { - try { - if (isConnected()) - try { - return session.getRootNode().hasNodes(); - } catch (AccessDeniedException e) { - // current user may not have access to the root node - return false; - } - else - return false; - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while checking children node existence", re); - } - } - - /** Override normal behaviour to initialize display of the workspace */ - @Override - public synchronized Object[] getChildren() { - if (isLoaded()) { - return super.getChildren(); - } else { - // initialize current object - try { - Node rootNode; - if (session == null) - return null; - else - rootNode = session.getRootNode(); - NodeIterator ni = rootNode.getNodes(); - while (ni.hasNext()) { - Node node = ni.nextNode(); - addChild(new SingleJcrNodeElem(this, node, node.getName())); - } - return super.getChildren(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot initialize WorkspaceNode UI object." + getName(), e); - } - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java deleted file mode 100644 index 8f5474449..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Model for SWT/JFace JCR components. */ -package org.argeo.cms.ui.jcr.model; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java deleted file mode 100644 index 26ae330b5..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace JCR components. */ -package org.argeo.cms.ui.jcr; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java deleted file mode 100644 index 82fdee796..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace components for Argeo CMS. */ -package org.argeo.cms.ui; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java deleted file mode 100644 index 3821e6045..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsStyle; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.jcr.JcrException; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; - -/** A link to an internal or external location. */ -public class CmsLink implements CmsUiProvider { - private final static CmsLog log = CmsLog.getLog(CmsLink.class); - private BundleContext bundleContext; - - private String label; - private String style; - private String target; - private String image; - private boolean openNew = false; - private MouseListener mouseListener; - - private int horizontalAlignment = SWT.CENTER; - private int verticalAlignment = SWT.CENTER; - - private String loggedInLabel = null; - private String loggedInTarget = null; - - // internal - // private Boolean isUrl = false; - private Integer imageWidth, imageHeight; - - public CmsLink() { - super(); - } - - public CmsLink(String label, String target) { - this(label, target, (String) null); - } - - public CmsLink(String label, String target, CmsStyle style) { - this(label, target, style != null ? style.style() : null); - } - - public CmsLink(String label, String target, String style) { - super(); - this.label = label; - this.target = target; - this.style = style; - init(); - } - - public void init() { - if (image != null) { - ImageData image = loadImage(); - if (imageHeight == null && imageWidth == null) { - imageWidth = image.width; - imageHeight = image.height; - } else if (imageHeight == null) { - imageHeight = (imageWidth * image.height) / image.width; - } else if (imageWidth == null) { - imageWidth = (imageHeight * image.width) / image.height; - } - } - } - - /** @return {@link Composite} with a single {@link Label} child. */ - @Override - public Control createUi(final Composite parent, Node context) { -// if (image != null && (imageWidth == null || imageHeight == null)) { -// throw new CmsException("Image is not properly configured." -// + " Make sure bundleContext property is set and init() method has been called."); -// } - - Composite comp = new Composite(parent, SWT.NONE); - comp.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - Label link = new Label(comp, SWT.NONE); - CmsSwtUtils.markup(link); - GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false); - if (image != null) { - if (imageHeight != null) - layoutData.heightHint = imageHeight; - if (label == null) - if (imageWidth != null) - layoutData.widthHint = imageWidth; - } - - link.setLayoutData(layoutData); - CmsSwtUtils.style(comp, style != null ? style : getDefaultStyle()); - CmsSwtUtils.style(link, style != null ? style : getDefaultStyle()); - - // label - StringBuilder labelText = new StringBuilder(); - if (loggedInTarget != null && isLoggedIn()) { - labelText.append(""); - } else if (target != null) { - labelText.append(""); - } - if (image != null) { - registerImageIfNeeded(); - String imageLocation = RWT.getResourceManager().getLocation(image); - labelText.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(""); - - link.setText(labelText.toString()); - - if (mouseListener != null) - link.addMouseListener(mouseListener); - - return comp; - } - - private void registerImageIfNeeded() { - ResourceManager resourceManager = RWT.getResourceManager(); - if (!resourceManager.isRegistered(image)) { - URL res = getImageUrl(); - try (InputStream inputStream = res.openStream()) { - resourceManager.register(image, inputStream); - if (log.isTraceEnabled()) - log.trace("Registered image " + image); - } catch (IOException e) { - throw new RuntimeException("Cannot load image " + image, e); - } - } - } - - private ImageData loadImage() { - URL url = getImageUrl(); - ImageData result = null; - try (InputStream inputStream = url.openStream()) { - result = new ImageData(inputStream); - if (log.isTraceEnabled()) - log.trace("Loaded image " + image); - } catch (IOException e) { - throw new RuntimeException("Cannot load image " + image, e); - } - return result; - } - - private URL getImageUrl() { - URL url; - try { - // pure URL - url = new URL(image); - } catch (MalformedURLException e1) { - url = bundleContext.getBundle().getResource(image); - } - - if (url == null) - throw new IllegalStateException("No image " + image + " available."); - - return url; - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - public void setLabel(String label) { - this.label = label; - } - - public void setStyle(String style) { - this.style = style; - } - - /** @deprecated Use {@link #setStyle(String)} instead. */ - @Deprecated - public void setCustom(String custom) { - this.style = custom; - } - - public void setTarget(String target) { - this.target = target; - // try { - // new URL(target); - // isUrl = true; - // } catch (MalformedURLException e1) { - // isUrl = false; - // } - } - - public void setImage(String image) { - this.image = image; - } - - public void setLoggedInLabel(String loggedInLabel) { - this.loggedInLabel = loggedInLabel; - } - - public void setLoggedInTarget(String loggedInTarget) { - this.loggedInTarget = loggedInTarget; - } - - public void setMouseListener(MouseListener mouseListener) { - this.mouseListener = mouseListener; - } - - public void setvAlign(String vAlign) { - if ("bottom".equals(vAlign)) { - verticalAlignment = SWT.BOTTOM; - } else if ("top".equals(vAlign)) { - verticalAlignment = SWT.TOP; - } else if ("center".equals(vAlign)) { - verticalAlignment = SWT.CENTER; - } else { - throw new IllegalArgumentException( - "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)"); - } - } - - protected boolean isLoggedIn() { - return !CurrentUser.isAnonymous(); - } - - public void setImageWidth(Integer imageWidth) { - this.imageWidth = imageWidth; - } - - public void setImageHeight(Integer imageHeight) { - this.imageHeight = imageHeight; - } - - public void setOpenNew(boolean openNew) { - this.openNew = openNew; - } - - protected String getDefaultStyle() { - return SimpleStyle.link.name(); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java deleted file mode 100644 index fc0c82146..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.argeo.cms.ui.util; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Composite; - -/** The main pane of a CMS display, with QA and support areas. */ -public class CmsPane { - - private Composite mainArea; - private Composite qaArea; - private Composite supportArea; - - public CmsPane(Composite parent, int style) { - parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); - -// qaArea = new Composite(parent, SWT.NONE); -// qaArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); -// RowLayout qaLayout = new RowLayout(); -// qaLayout.spacing = 0; -// qaArea.setLayout(qaLayout); - - mainArea = new Composite(parent, SWT.NONE); - mainArea.setLayout(new GridLayout()); - mainArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - -// supportArea = new Composite(parent, SWT.NONE); -// supportArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); -// RowLayout supportLayout = new RowLayout(); -// supportLayout.spacing = 0; -// supportArea.setLayout(supportLayout); - } - - public Composite getMainArea() { - return mainArea; - } - - public Composite getQaArea() { - return qaArea; - } - - public Composite getSupportArea() { - return supportArea; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java deleted file mode 100644 index 8b384799f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.StringTokenizer; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.servlet.http.HttpServletRequest; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Table; - -/** Static utilities for the CMS framework. */ -public class CmsUiUtils { - // private final static Log log = LogFactory.getLog(CmsUiUtils.class); - - /* - * CMS VIEW - */ - - /** - * The CMS view related to this display, or null if none is available from this - * call. - * - * @deprecated Use {@link CmsSwtUtils#getCmsView(Composite)} instead. - */ - @Deprecated - public static CmsView getCmsView() { -// return UiContext.getData(CmsView.class.getName()); - return CmsSwtUtils.getCmsView(Display.getCurrent().getActiveShell()); - } - - public static StringBuilder getServerBaseUrl(HttpServletRequest request) { - try { - URL url = new URL(request.getRequestURL().toString()); - StringBuilder buf = new StringBuilder(); - buf.append(url.getProtocol()).append("://").append(url.getHost()); - if (url.getPort() != -1) - buf.append(':').append(url.getPort()); - return buf; - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot extract server base URL from " + request.getRequestURL(), e); - } - } - - // - public static String getDataUrl(Node node, HttpServletRequest request) { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append(getDataPath(node)); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot build data URL for " + node, e); - } - } - - /** A path in the node repository */ - public static String getDataPath(Node node) { - return getDataPath(CmsConstants.EGO_REPOSITORY, node); - } - - public static String getDataPath(String cn, Node node) { - return CmsJcrUtils.getDataPath(cn, node); - } - - /** Clean reserved URL characters for use in HTTP links. */ - public static String getDataPathForUrl(Node node) { - return cleanPathForUrl(getDataPath(node)); - } - - /** Clean reserved URL characters for use in HTTP links. */ - public static String cleanPathForUrl(String path) { - StringTokenizer st = new StringTokenizer(path, "/"); - StringBuilder sb = new StringBuilder(); - while (st.hasMoreElements()) { - sb.append('/'); - String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8); - encoded = encoded.replace("+", "%20"); - sb.append(encoded); - - } - return sb.toString(); - } - - /** @deprecated Use rowData16px() instead. GridData should not be reused. */ - @Deprecated - public static RowData ROW_DATA_16px = new RowData(16, 16); - - - - /* - * FORM LAYOUT - */ - - - - @Deprecated - public static void setItemHeight(Table table, int height) { - table.setData(CmsUiConstants.ITEM_HEIGHT, height); - } - - // - // JCR - // - public static Node getOrAddEmptyFile(Node parent, Enum child) throws RepositoryException { - if (has(parent, child)) - return child(parent, child); - return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]); - } - - public static Node child(Node parent, Enum en) throws RepositoryException { - return parent.getNode(en.name()); - } - - public static Boolean has(Node parent, Enum en) throws RepositoryException { - return parent.hasNode(en.name()); - } - - public static Node getOrAdd(Node parent, Enum en) throws RepositoryException { - return getOrAdd(parent, en, null); - } - - public static Node getOrAdd(Node parent, Enum en, String primaryType) throws RepositoryException { - if (has(parent, en)) - return child(parent, en); - else if (primaryType == null) - return parent.addNode(en.name()); - else - return parent.addNode(en.name(), primaryType); - } - - // IMAGES - - public static String img(Node fileNode, String width, String height) { - return img(null, fileNode, width, height); - } - - public static String img(String serverBase, Node fileNode, String width, String height) { -// String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode); - String src; - src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode); - return imgBuilder(src, width, height).append("/>").toString(); - } - - public static String img(String src, String width, String height) { - return imgBuilder(src, width, height).append("/>").toString(); - } - - public static String img(String src, Cms2DSize size) { - return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); - } - - public static StringBuilder imgBuilder(String src, String width, String height) { - return new StringBuilder(64).append(" { - private final static CmsLog log = CmsLog.getLog(DefaultImageManager.class); -// private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); - - public Boolean load(Node node, Control control, Cms2DSize preferredSize) { - Cms2DSize imageSize = getImageSize(node); - Cms2DSize size; - String imgTag = null; - if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0 - || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) { - if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) { - // actual image size if completely known - size = imageSize; - } else { - // no image if not completely known - size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize); - imgTag = CmsUiUtils.noImg(size); - } - - } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) { - // given size if completely provided - size = preferredSize; - } else { - // at this stage : - // image is completely known - assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0; - // one and only one of the dimension as been specified - assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0; - size = resizeTo(imageSize, preferredSize); - } - - boolean loaded = false; - if (control == null) - return loaded; - - if (control instanceof Label) { - if (imgTag == null) { - // IMAGE RETRIEVED HERE - imgTag = getImageTag(node, size); - // - if (imgTag == null) - imgTag = CmsUiUtils.noImg(size); - else - loaded = true; - } - - Label lbl = (Label) control; - lbl.setText(imgTag); - // lbl.setSize(size); - } else if (control instanceof FileUpload) { - FileUpload lbl = (FileUpload) control; - lbl.setImage(CmsUiUtils.noImage(size)); - lbl.setSize(new Point(size.getWidth(), size.getHeight())); - return loaded; - } else - loaded = false; - - return loaded; - } - - private Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) { - if (constraints.getWidth() != 0 && constraints.getHeight() != 0) { - return constraints; - } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) { - return orig; - } else if (constraints.getHeight() == 0) {// force width - return new Cms2DSize(constraints.getWidth(), - scale(orig.getHeight(), orig.getWidth(), constraints.getWidth())); - } else if (constraints.getWidth() == 0) {// force height - return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()), - constraints.getHeight()); - } - throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints); - } - - private int scale(int origDimension, int otherDimension, int otherConstraint) { - return Math.round(origDimension * divide(otherConstraint, otherDimension)); - } - - private float divide(int a, int b) { - return ((float) a) / ((float) b); - } - - public Cms2DSize getImageSize(Node node) { - // TODO optimise - Image image = getSwtImage(node); - return new Cms2DSize(image.getBounds().width, image.getBounds().height); - } - - /** @return null if not available */ - @Override - public String getImageTag(Node node) { - return getImageTag(node, getImageSize(node)); - } - - private String getImageTag(Node node, Cms2DSize size) { - StringBuilder buf = getImageTagBuilder(node, size); - if (buf == null) - return null; - return buf.append("/>").toString(); - } - - /** @return null if not available */ - @Override - public StringBuilder getImageTagBuilder(Node node, Cms2DSize size) { - return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); - } - - /** @return null if not available */ - private StringBuilder getImageTagBuilder(Node node, String width, String height) { - String url = getImageUrl(node); - if (url == null) - return null; - return CmsUiUtils.imgBuilder(url, width, height); - } - - /** @return null if not available */ - @Override - public String getImageUrl(Node node) { - return CmsUiUtils.getDataPathForUrl(node); - } - - protected String getResourceName(Node node) { - try { - String workspace = node.getSession().getWorkspace().getName(); - if (node.hasNode(JCR_CONTENT)) - return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier(); - else - return workspace + '_' + node.getIdentifier(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - public Binary getImageBinary(Node node) { - try { - if (node.isNodeType(NT_FILE)) { - return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary(); - } else { - return null; - } - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - public Image getSwtImage(Node node) { - InputStream inputStream = null; - Binary binary = getImageBinary(node); - if (binary == null) - return null; - try { - inputStream = binary.getStream(); - return new Image(Display.getCurrent(), inputStream); - } catch (RepositoryException e) { - throw new JcrException(e); - } finally { - IOUtils.closeQuietly(inputStream); - JcrUtils.closeQuietly(binary); - } - } - - @Override - public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType) { - InputStream inputStream = null; - try { - String previousResourceName = null; - if (parentNode.hasNode(fileName)) { - Node node = parentNode.getNode(fileName); - previousResourceName = getResourceName(node); - if (node.hasNode(JCR_CONTENT)) { - node.getNode(JCR_CONTENT).remove(); - node.addNode(JCR_CONTENT, NT_RESOURCE); - } - } - - byte[] arr = IOUtils.toByteArray(in); - Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr); - inputStream = new ByteArrayInputStream(arr); - ImageData id = new ImageData(inputStream); - processNewImageFile(context, fileNode, id); - - String mime = contentType != null ? contentType : Files.probeContentType(Paths.get(fileName)); - if (mime != null) { - fileNode.getNode(JCR_CONTENT).setProperty(Property.JCR_MIMETYPE, mime); - } - fileNode.getSession().save(); - - // reset resource manager - ResourceManager resourceManager = RWT.getResourceManager(); - if (previousResourceName != null && resourceManager.isRegistered(previousResourceName)) { - resourceManager.unregister(previousResourceName); - if (log.isDebugEnabled()) - log.debug("Unregistered image " + previousResourceName); - } - return CmsUiUtils.getDataPath(fileNode); - } catch (IOException e) { - throw new RuntimeException("Cannot upload image " + fileName + " in " + parentNode, e); - } catch (RepositoryException e) { - throw new JcrException(e); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - /** Does nothing by default. */ - protected void processNewImageFile(Node context, Node fileNode, ImageData id) - throws RepositoryException, IOException { - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java deleted file mode 100644 index 284d2bd0c..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.cms.ui.util; - -import org.argeo.cms.swt.CmsStyles; - -/** - * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on - * a {@link CmsLink} when simple menus are used. - */ -public class MenuLink extends CmsLink { - public MenuLink() { - setCustom(CmsStyles.CMS_MENU_LINK); - } - - public MenuLink(String label, String target, String custom) { - super(label, target, custom); - } - - public MenuLink(String label, String target) { - super(label, target, CmsStyles.CMS_MENU_LINK); - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java deleted file mode 100644 index e8bf66297..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** A header in three parts */ -public class SimpleCmsHeader implements CmsUiProvider { - private List lead = new ArrayList(); - private List center = new ArrayList(); - private List end = new ArrayList(); - - 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 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 lead) { - this.lead = lead; - } - - public void setCenter(List center) { - this.center = center; - } - - public void setEnd(List end) { - this.end = end; - } - - public void setSubPartsSameWidth(Boolean subPartsSameWidth) { - this.subPartsSameWidth = subPartsSameWidth; - } - - public List getLead() { - return lead; - } - - public List getCenter() { - return center; - } - - public List getEnd() { - return end; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java deleted file mode 100644 index 8e0e7c179..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java +++ /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("" + context.getName() + ""); - 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("Children:"); - // 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("Properties:"); - // 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 = ""; - else if (value.getType() == PropertyType.DATE) - result = timeFormatter.format(value.getDate().getTime()); - else - result = value.getString(); - } - return result; - } - - private String getMultiAsString(Property property, String separator) - throws RepositoryException { - if (separator == null) - separator = "; "; - Value[] values = property.getValues(); - StringBuilder builder = new StringBuilder(); - for (Value val : values) { - String currStr = val.getString(); - if (!"".equals(currStr.trim())) - builder.append(currStr).append(separator); - } - if (builder.lastIndexOf(separator) >= 0) - return builder.substring(0, builder.length() - separator.length()); - else - return builder.toString(); - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java deleted file mode 100644 index ac09b2a02..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.argeo.cms.ui.util; - -public class SimpleImageManager extends DefaultImageManager { - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java deleted file mode 100644 index 63e504b7a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms.ui.util; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -public class SimpleStaticPage implements CmsUiProvider { - private String text; - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Label textC = new Label(parent, SWT.WRAP); - textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT); - textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); - textC.setText(text); - - return textC; - } - - public void setText(String text) { - this.text = text; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java deleted file mode 100644 index 8ed06a292..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.ui.util; - -import org.argeo.api.cms.CmsStyle; - -/** Simple styles used by the CMS UI utilities. */ -public enum SimpleStyle implements CmsStyle { - link; -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java deleted file mode 100644 index ad1523c1a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java +++ /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 stylesheets = new LinkedHashMap(); - - public StyleSheetResourceLoader(Bundle themeBundle) { - this.themeBundle = themeBundle; - } - - @Override - public InputStream getResourceAsStream(String resourceName) throws IOException { - if (!stylesheets.containsKey(resourceName)) { - // TODO deal with other bundles - // Bundle bundle = bundleContext.getBundle(); - // String location = - // bundle.getLocation().substring("initial@reference:".length()); - // if (location.startsWith("file:")) { - // Path path = null; - // try { - // path = Paths.get(new URI(location)); - // } catch (URISyntaxException e) { - // e.printStackTrace(); - // } - // if (path != null) { - // Path resourcePath = path.resolve(resourceName); - // if (Files.exists(resourcePath)) - // return Files.newInputStream(resourcePath); - // } - // } - - URL res = themeBundle.getEntry(resourceName); - if (res == null) - throw new CmsException( - "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - IOUtils.copy(res.openStream(), out); - stylesheets.put(resourceName, new StyleSheet(out.toByteArray())); - } - return new ByteArrayInputStream(stylesheets.get(resourceName).getData()); - // return res.openStream(); - } - - private class StyleSheet { - private byte[] data; - - public StyleSheet(byte[] data) { - super(); - this.data = data; - } - - public byte[] getData() { - return data; - } - - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java deleted file mode 100644 index 156a6082f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Shell displaying system notifications such as exceptions */ -public class SystemNotifications extends Shell implements CmsStyles, - MouseListener { - private static final long serialVersionUID = -8129377525216022683L; - - private Control source; - - public SystemNotifications(Control source) { - super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); - - this.source = source; - - // TODO UI - // setLocation(source.toDisplay(source.getSize().x - getSize().x, - // source.getSize().y)); - setLayout(new GridLayout()); - addMouseListener(this); - - addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - close(); - dispose(); - } - }); - - } - - public void notifyException(Throwable exception) { - Composite pane = this; - - Label lbl = new Label(pane, SWT.NONE); - lbl.setText(exception.getLocalizedMessage() - + (exception instanceof CmsException ? "" : "(" - + exception.getClass().getName() + ")") + "\n"); - lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - lbl.addMouseListener(this); - if (exception.getCause() != null) - appendCause(pane, exception.getCause()); - - StringBuilder mailToUrl = new StringBuilder("mailto:?"); - try { - mailToUrl.append("subject=").append( - URLEncoder.encode( - "Exception " - + new SimpleDateFormat("yyyy-MM-dd hh:mm") - .format(new Date()), "UTF-8") - .replace("+", "%20")); - - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - IOUtils.closeQuietly(sw); - - // see - // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character - String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace( - "+", "%20"); - mailToUrl.append("&body=").append(encoded); - } catch (UnsupportedEncodingException e) { - mailToUrl.append("&body=").append("Could not encode: ") - .append(e.getMessage()); - } - Label mailTo = new Label(pane, SWT.NONE); - CmsSwtUtils.markup(mailTo); - mailTo.setText("Send details"); - mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - - pack(); - layout(); - - setLocation(source.toDisplay(source.getSize().x - getSize().x, - source.getSize().y - getSize().y)); - open(); - } - - private void appendCause(Composite parent, Throwable e) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(" caused by: " + e.getLocalizedMessage() + " (" - + e.getClass().getName() + ")" + "\n"); - lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - lbl.addMouseListener(this); - if (e.getCause() != null) - appendCause(parent, e.getCause()); - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - - @Override - public void mouseDown(MouseEvent e) { - close(); - dispose(); - } - - @Override - public void mouseUp(MouseEvent e) { - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java deleted file mode 100644 index 008ec2c16..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.argeo.cms.ui.util; - -import javax.jcr.Node; - -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.auth.CmsLoginShell; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** The site-related user menu */ -public class UserMenu extends CmsLoginShell { - private final Control source; - private final Node context; - - public UserMenu(Control source, Node context) { - // FIXME pass CMS context - super(CmsUiUtils.getCmsView(), null); - this.context = context; - createUi(); - if (source == null) - throw new CmsException("Source control cannot be null."); - this.source = source; - open(); - } - - @Override - protected Shell createShell() { - return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - } - - @Override - public void open() { - Shell shell = getShell(); - shell.pack(); - shell.layout(); - shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y)); - shell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - closeShell(); - } - }); - super.open(); - } - - protected Node getContext() { - return context; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java deleted file mode 100644 index 317a7b55b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.cms.ui.util; - -import javax.jcr.Node; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.auth.CmsLoginShell; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -/** Open the user menu when clicked */ -public class UserMenuLink extends MenuLink { - - public UserMenuLink() { - setCustom(CmsStyles.CMS_USER_MENU_LINK); - } - - @Override - public Control createUi(Composite parent, Node context) { - if (CurrentUser.isAnonymous()) - setLabel(CmsMsg.login.lead()); - else { - setLabel(CurrentUser.getDisplayName()); - } - Label link = (Label) ((Composite) super.createUi(parent, context)).getChildren()[0]; - link.addMouseListener(new UserMenuLinkController(context)); - return link.getParent(); - } - - protected CmsLoginShell createUserMenu(Control source, Node context) { - return new UserMenu(source.getParent(), context); - } - - private class UserMenuLinkController implements MouseListener, DisposeListener { - private static final long serialVersionUID = 3634864186295639792L; - - private CmsLoginShell userMenu = null; - private long lastDisposeTS = 0l; - - private final Node context; - - public UserMenuLinkController(Node context) { - this.context = context; - } - - // - // MOUSE LISTENER - // - @Override - public void mouseDown(MouseEvent e) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - if (userMenu == null) { - long durationSinceLastDispose = System.currentTimeMillis() - lastDisposeTS; - // avoid to reopen the menu, if one has clicked gain - if (durationSinceLastDispose > 200) { - userMenu = createUserMenu(source, context); - userMenu.getShell().addDisposeListener(this); - } - } - } - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void widgetDisposed(DisposeEvent event) { - userMenu = null; - lastDisposeTS = System.currentTimeMillis(); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java deleted file mode 100644 index 7f846c932..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -public class VerticalMenu implements CmsUiProvider { - private List items = new ArrayList(); - - @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 getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java deleted file mode 100644 index 566df883c..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS UI utilities. */ -package org.argeo.cms.ui.util; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java deleted file mode 100644 index ef24ee0d5..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java +++ /dev/null @@ -1,350 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Observable; -import java.util.Observer; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; - -import org.argeo.api.cms.CmsEditable; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ui.widgets.ScrolledPage; -import org.argeo.jcr.JcrException; -import org.eclipse.jface.viewers.ContentViewer; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Widget; -import org.xml.sax.SAXParseException; - -/** Base class for viewers related to a page */ -public abstract class AbstractPageViewer extends ContentViewer implements Observer { - private static final long serialVersionUID = 5438688173410341485L; - - private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class); - - private final boolean readOnly; - /** The basis for the layouts, typically a ScrolledPage. */ - private final Composite page; - private final CmsEditable cmsEditable; - - private MouseListener mouseListener; - private FocusListener focusListener; - - private EditablePart edited; - private ISelection selection = StructuredSelection.EMPTY; - - private AccessControlContext accessControlContext; - - protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) { - // read only at UI level - readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - - this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - - if (cmsEditable.canEdit()) { - mouseListener = createMouseListener(); - focusListener = createFocusListener(); - } - page = findPage(parent); - accessControlContext = AccessController.getContext(); - } - - /** - * Can be called to simplify the called to isModelInitialized() and initModel() - */ - protected void initModelIfNeeded(Node node) { - try { - if (!isModelInitialized(node)) - if (getCmsEditable().canEdit()) { - initModel(node); - node.getSession().save(); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot initialize model", e); - } - } - - /** Called if user can edit and model is not initialized */ - protected Boolean isModelInitialized(Node node) throws RepositoryException { - return true; - } - - /** Called if user can edit and model is not initialized */ - protected void initModel(Node node) throws RepositoryException { - } - - /** Create (retrieve) the MouseListener to use. */ - protected MouseListener createMouseListener() { - return new MouseAdapter() { - private static final long serialVersionUID = 1L; - }; - } - - /** Create (retrieve) the FocusListener to use. */ - protected FocusListener createFocusListener() { - return new FocusListener() { - private static final long serialVersionUID = 1L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - } - }; - } - - protected Composite findPage(Composite composite) { - if (composite instanceof ScrolledPage) { - return (ScrolledPage) composite; - } else { - if (composite.getParent() == null) - return composite; - return findPage(composite.getParent()); - } - } - - public void layoutPage() { - if (page != null) - page.layout(true, true); - } - - protected void showControl(Control control) { - if (page != null && (page instanceof ScrolledPage)) - ((ScrolledPage) page).showControl(control); - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) - editingStateChanged(cmsEditable); - } - - /** To be overridden in order to provide the actual refresh */ - protected void refresh(Control control) throws RepositoryException { - } - - /** To be overridden.Save the edited part. */ - protected void save(EditablePart part) throws RepositoryException { - } - - /** Prepare the edited part */ - protected void prepare(EditablePart part, Object caretPosition) { - } - - /** Notified when the editing state changed. Does nothing, to be overridden */ - protected void editingStateChanged(CmsEditable cmsEditable) { - } - - @Override - public void refresh() { - // TODO check actual context in order to notice a discrepancy - Subject viewerSubject = getViewerSubject(); - Subject.doAs(viewerSubject, (PrivilegedAction) () -> { - try { - if (cmsEditable.canEdit() && !readOnly) - mouseListener = createMouseListener(); - else - mouseListener = null; - refresh(getControl()); - // layout(getControl()); - if (!getControl().isDisposed()) - layoutPage(); - } catch (RepositoryException e) { - throw new JcrException("Cannot refresh", e); - } - return null; - }); - } - - @Override - public void setSelection(ISelection selection, boolean reveal) { - this.selection = selection; - } - - protected void updateContent(EditablePart part) throws RepositoryException { - } - - // LOW LEVEL EDITION - protected void edit(EditablePart part, Object caretPosition) { - try { - if (edited == part) - return; - - if (edited != null && edited != part) { - EditablePart previouslyEdited = edited; - try { - stopEditing(true); - } catch (Exception e) { - notifyEditionException(e); - edit(previouslyEdited, caretPosition); - return; - } - } - - part.startEditing(); - edited = part; - updateContent(part); - prepare(part, caretPosition); - edited.getControl().addFocusListener(new FocusListener() { - private static final long serialVersionUID = 6883521812717097017L; - - @Override - public void focusLost(FocusEvent event) { - stopEditing(true); - } - - @Override - public void focusGained(FocusEvent event) { - } - }); - - layout(part.getControl()); - showControl(part.getControl()); - } catch (RepositoryException e) { - throw new JcrException("Cannot edit " + part, e); - } - } - - protected void stopEditing(Boolean save) { - if (edited instanceof Widget && ((Widget) edited).isDisposed()) { - edited = null; - return; - } - - assert edited != null; - if (edited == null) { - if (log.isTraceEnabled()) - log.warn("Told to stop editing while not editing anything"); - return; - } - - try { - if (save) - save(edited); - - edited.stopEditing(); - EditablePart editablePart = edited; - Control control = ((EditablePart) edited).getControl(); - edited = null; - // TODO make edited state management more robust - updateContent(editablePart); - layout(control); - } catch (RepositoryException e) { - throw new JcrException("Cannot stop editing", e); - } finally { - edited = null; - } - } - - // METHODS AVAILABLE TO EXTENDING CLASSES - protected void saveEdit() { - if (edited != null) - stopEditing(true); - } - - protected void cancelEdit() { - if (edited != null) - stopEditing(false); - } - - /** Layout this controls from the related base page. */ - public void layout(Control... controls) { - page.layout(controls); - } - - /** - * Find the first {@link EditablePart} in the parents hierarchy of this control - */ - protected EditablePart findDataParent(Control parent) { - if (parent instanceof EditablePart) { - return (EditablePart) parent; - } - if (parent.getParent() != null) - return findDataParent(parent.getParent()); - else - throw new IllegalStateException("No data parent found"); - } - - // UTILITIES - /** Check whether the edited part is in a proper state */ - protected void checkEdited() { - if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed()) - throw new IllegalStateException("Edited should not be null or disposed at this stage"); - } - - /** Persist all changes. */ - protected void persistChanges(Session session) throws RepositoryException { - session.save(); - session.refresh(false); - // TODO notify that changes have been persisted - } - - /** Convenience method using a Node in order to save the underlying session. */ - protected void persistChanges(Node anyNode) throws RepositoryException { - persistChanges(anyNode.getSession()); - } - - /** Notify edition exception */ - protected void notifyEditionException(Throwable e) { - Throwable eToLog = e; - if (e instanceof IllegalArgumentException) - if (e.getCause() instanceof SAXParseException) - eToLog = e.getCause(); - log.error(eToLog.getMessage(), eToLog); -// if (log.isTraceEnabled()) -// log.trace("Full stack of " + eToLog.getMessage(), e); - // TODO Light error notification popup - } - - protected Subject getViewerSubject() { - Subject res = null; - if (accessControlContext != null) { - res = Subject.getSubject(accessControlContext); - } - if (res == null) - throw new IllegalStateException("No subject associated with this viewer"); - return res; - } - - // GETTERS / SETTERS - public boolean isReadOnly() { - return readOnly; - } - - protected EditablePart getEdited() { - return edited; - } - - public MouseListener getMouseListener() { - return mouseListener; - } - - public FocusListener getFocusListener() { - return focusListener; - } - - public CmsEditable getCmsEditable() { - return cmsEditable; - } - - @Override - public ISelection getSelection() { - return selection; - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java deleted file mode 100644 index 3967c97fb..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import org.eclipse.swt.widgets.Control; - -/** Manages whether an editable or non editable control is shown. */ -public interface EditablePart { - public void startEditing(); - - public void stopEditing(); - - public Control getControl(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java deleted file mode 100644 index 4ca45d191..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java +++ /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 { - public Item getItem() throws RepositoryException; -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java deleted file mode 100644 index 11162e87f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import java.util.Observable; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.version.VersionManager; - -import org.argeo.api.cms.CmsEditable; -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.CmsEditionEvent; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; - -/** Provides the CmsEditable semantic based on JCR versioning. */ -public class JcrVersionCmsEditable extends Observable implements CmsEditable { - private final String nodePath;// cache - private final VersionManager versionManager; - private final Boolean canEdit; - - public JcrVersionCmsEditable(Node node) throws RepositoryException { - this.nodePath = node.getPath(); - if (node.getSession().hasPermission(node.getPath(), - Session.ACTION_SET_PROPERTY)) { - // was Session.ACTION_ADD_NODE - canEdit = true; - if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) { - node.addMixin(NodeType.MIX_VERSIONABLE); - node.getSession().save(); - } - versionManager = node.getSession().getWorkspace() - .getVersionManager(); - } else { - canEdit = false; - versionManager = null; - } - - // bind keys - if (canEdit) { - Display display = Display.getCurrent(); - display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN", - "CTRL+E" }); - display.addFilter(SWT.KeyDown, new Listener() { - private static final long serialVersionUID = -4378653870463187318L; - - public void handleEvent(Event e) { - boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0; - if (ctrlPressed && e.keyCode == '\r') - stopEditing(); - else if (ctrlPressed && e.keyCode == 'E') - stopEditing(); - } - }); - } - } - - @Override - public Boolean canEdit() { - return canEdit; - } - - public Boolean isEditing() { - try { - if (!canEdit()) - return false; - return versionManager.isCheckedOut(nodePath); - } catch (RepositoryException e) { - throw new CmsException("Cannot check whether " + nodePath - + " is editing", e); - } - } - - @Override - public void startEditing() { - try { - versionManager.checkout(nodePath); - setChanged(); - } catch (RepositoryException e1) { - throw new CmsException("Cannot publish " + nodePath); - } - notifyObservers(new CmsEditionEvent(nodePath, - CmsEditionEvent.START_EDITING)); - } - - @Override - public void stopEditing() { - try { - versionManager.checkin(nodePath); - setChanged(); - } catch (RepositoryException e1) { - throw new CmsException("Cannot publish " + nodePath, e1); - } - notifyObservers(new CmsEditionEvent(nodePath, - CmsEditionEvent.STOP_EDITING)); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java deleted file mode 100644 index b51d4fcba..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java +++ /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 { - public Node getNode(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java deleted file mode 100644 index 793079e20..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java +++ /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 { - public Property getProperty(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java deleted file mode 100644 index d282eebbe..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.widgets.JcrComposite; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** A structured UI related to a JCR context. */ -public class Section extends JcrComposite { - private static final long serialVersionUID = -5933796173755739207L; - - private final Section parentSection; - private Composite sectionHeader; - private final Integer relativeDepth; - - public Section(Composite parent, int style, Node node) { - this(parent, findSection(parent), style, node); - } - - public Section(Section section, int style, Node node) { - this(section, section, style, node); - } - - protected Section(Composite parent, Section parentSection, int style, Node node) { - super(parent, style, node); - try { - this.parentSection = parentSection; - if (parentSection != null) { - relativeDepth = getNode().getDepth() - parentSection.getNode().getDepth(); - } else { - relativeDepth = 0; - } - setLayout(CmsSwtUtils.noSpaceGridLayout()); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create section from " + node, e); - } - } - - public Map getSubSections() throws RepositoryException { - LinkedHashMap result = new LinkedHashMap(); - for (Control child : getChildren()) { - if (child instanceof Composite) { - collectDirectSubSections((Composite) child, result); - } - } - return Collections.unmodifiableMap(result); - } - - private void collectDirectSubSections(Composite composite, LinkedHashMap subSections) - throws RepositoryException { - if (composite == sectionHeader || composite instanceof EditablePart) - return; - if (composite instanceof Section) { - Section section = (Section) composite; - subSections.put(section.getNodeId(), section); - return; - } - - for (Control child : composite.getChildren()) - if (child instanceof Composite) - collectDirectSubSections((Composite) child, subSections); - } - - public Composite createHeader() { - return createHeader(this); - } - - public Composite createHeader(Composite parent) { - if (sectionHeader != null) - sectionHeader.dispose(); - - sectionHeader = new Composite(parent, SWT.NONE); - sectionHeader.setLayoutData(CmsSwtUtils.fillWidth()); - sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout()); - // sectionHeader.moveAbove(null); - // layout(); - return sectionHeader; - } - - public Composite getHeader() { - if (sectionHeader != null && sectionHeader.isDisposed()) - sectionHeader = null; - return sectionHeader; - } - - // SECTION PARTS - public SectionPart getSectionPart(String partId) { - for (Control child : getChildren()) { - if (child instanceof SectionPart) { - SectionPart sectionPart = (SectionPart) child; - if (sectionPart.getPartId().equals(partId)) - return sectionPart; - } - } - return null; - } - - public SectionPart nextSectionPart(SectionPart sectionPart) { - Control[] children = getChildren(); - for (int i = 0; i < children.length; i++) { - if (sectionPart == children[i]) { - for (int j = i + 1; j < children.length; j++) { - if (children[i + 1] instanceof SectionPart) { - return (SectionPart) children[i + 1]; - } - } - -// if (i + 1 < children.length) { -// Composite next = (Composite) children[i + 1]; -// return (SectionPart) next; -// } else { -// // next section -// } - } - } - return null; - } - - public SectionPart previousSectionPart(SectionPart sectionPart) { - Control[] children = getChildren(); - for (int i = 0; i < children.length; i++) { - if (sectionPart == children[i]) - if (i != 0) { - Composite previous = (Composite) children[i - 1]; - return (SectionPart) previous; - } else { - // previous section - } - } - return null; - } - - @Override - public String toString() { - if (parentSection == null) - return "Main section " + getNode(); - return "Section " + getNode(); - } - - public Section getParentSection() { - return parentSection; - } - - public Integer getRelativeDepth() { - return relativeDepth; - } - - /** Recursively finds the related section in the parents (can be itself) */ - public static Section findSection(Control control) { - if (control == null) - return null; - if (control instanceof Section) - return (Section) control; - else - return findSection(control.getParent()); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java deleted file mode 100644 index f0b367f5a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.cms.ui.viewers; - - -/** An editable part dynamically related to a Section */ -public interface SectionPart extends EditablePart, NodePart { - public String getPartId(); - - public Section getSection(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java deleted file mode 100644 index 2f0793127..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS generic viewers, based on JFace. */ -package org.argeo.cms.ui.viewers; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java deleted file mode 100644 index 7bc0f79b5..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Shell; - -/** - * Manages a lightweight shell which is related to a {@link Control}, typically - * in order to reproduce a dropdown semantic, but with more flexibility. - */ -public class ContextOverlay extends ScrolledPage { - private static final long serialVersionUID = 6702077429573324009L; - -// private Shell shell; - private Control control; - - private int maxHeight = 400; - - public ContextOverlay(Control control, int style) { - super(createShell(control, style), SWT.NONE); - Shell shell = getShell(); - setLayoutData(CmsSwtUtils.fillAll()); - // TODO make autohide configurable? - //shell.addShellListener(new AutoHideShellListener()); - this.control = control; - control.addDisposeListener((e) -> { - dispose(); - shell.dispose(); - }); - } - - private static Composite createShell(Control control, int style) { - if (control == null) - throw new IllegalArgumentException("Control cannot be null"); - if (control.isDisposed()) - throw new IllegalArgumentException("Control is disposed"); - Shell shell = new Shell(control.getShell(), SWT.NO_TRIM); - shell.setLayout(CmsSwtUtils.noSpaceGridLayout()); - Composite placeholder = new Composite(shell, SWT.BORDER); - placeholder.setLayoutData(CmsSwtUtils.fillAll()); - placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout()); - return placeholder; - } - - public void show() { - Point relativeControlLocation = control.getLocation(); - Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y); - - int controlWidth = control.getBounds().width; - - Shell shell = getShell(); - - layout(true, true); - shell.pack(); - shell.layout(true, true); - int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x; - if (shell.getSize().y > maxHeight) { - shell.setSize(targetShellWidth, maxHeight); - } else { - shell.setSize(targetShellWidth, shell.getSize().y); - } - - int shellHeight = shell.getSize().y; - int controlHeight = control.getBounds().height; - Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight); - int displayHeight = shell.getDisplay().getBounds().height; - if (shellLocation.y + shellHeight > displayHeight) {// bottom of page - shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight); - } - shell.setLocation(shellLocation); - - if (getChildren().length != 0) - shell.open(); - if (!control.isDisposed()) - control.setFocus(); - } - - public void hide() { - getShell().setVisible(false); - onHide(); - } - - public boolean isShellVisible() { - if (isDisposed()) - return false; - return getShell().isVisible(); - } - - /** to be overridden */ - protected void onHide() { - // does nothing by default. - } - - private class AutoHideShellListener extends ShellAdapter { - private static final long serialVersionUID = 7743287433907938099L; - - @Override - public void shellDeactivated(ShellEvent e) { - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - // silent - } - if (!control.isDisposed() && !control.isFocusControl()) - hide(); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java deleted file mode 100644 index c2393f267..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** A stylable and editable image. */ -public abstract class EditableImage extends StyledControl { - private static final long serialVersionUID = -5689145523114022890L; - private final static CmsLog log = CmsLog.getLog(EditableImage.class); - - private Cms2DSize preferredImageSize; - private Boolean loaded = false; - - public EditableImage(Composite parent, int swtStyle) { - super(parent, swtStyle); - } - - public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) { - super(parent, swtStyle); - this.preferredImageSize = preferredImageSize; - } - - public EditableImage(Composite parent, int style, Node node, boolean cacheImmediately, Cms2DSize preferredImageSize) - throws RepositoryException { - super(parent, style, node, cacheImmediately); - this.preferredImageSize = preferredImageSize; - } - - @Override - protected void setContainerLayoutData(Composite composite) { - // composite.setLayoutData(fillWidth()); - } - - @Override - protected void setControlLayoutData(Control control) { - // control.setLayoutData(fillWidth()); - } - - /** To be overriden. */ - protected String createImgTag() throws RepositoryException { - return CmsUiUtils - .noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y)); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle()); - // lbl.setLayoutData(CmsUiUtils.fillWidth()); - CmsSwtUtils.markup(lbl); - CmsSwtUtils.style(lbl, style); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - load(lbl); - return lbl; - } - - /** To be overriden. */ - protected synchronized Boolean load(Control control) { - String imgTag; - try { - imgTag = createImgTag(); - } catch (Exception e) { - // throw new CmsException("Cannot retrieve image", e); - log.error("Cannot retrieve image", e); - imgTag = CmsUiUtils.noImg(preferredImageSize); - loaded = false; - } - - if (imgTag == null) { - loaded = false; - imgTag = CmsUiUtils.noImg(preferredImageSize); - } else - loaded = true; - if (control != null) { - ((Label) control).setText(imgTag); - control.setSize(preferredImageSize != null - ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight()) - : getSize()); - } else { - loaded = false; - } - getParent().layout(); - return loaded; - } - - public void setPreferredSize(Cms2DSize size) { - this.preferredImageSize = size; - if (!loaded) { - load((Label) getControl()); - } - } - - protected Text createText(Composite box, String style) { - Text text = new Text(box, getStyle()); - CmsSwtUtils.style(text, style); - return text; - } - - public Cms2DSize getPreferredImageSize() { - return preferredImageSize; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java deleted file mode 100644 index e3499ac4b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable text part displaying styled text. */ -public class EditableText extends StyledControl { - private static final long serialVersionUID = -6372283442330912755L; - - private boolean editable = true; - - private Color highlightColor; - private Composite highlight; - - private boolean useTextAsLabel = false; - - public EditableText(Composite parent, int style) { - super(parent, style); - editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); - highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); - } - - public EditableText(Composite parent, int style, Item item) throws RepositoryException { - this(parent, style, item, false); - } - - public EditableText(Composite parent, int style, Item item, boolean cacheImmediately) throws RepositoryException { - super(parent, style, item, cacheImmediately); - editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); - highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing() && getEditable()) { - return createText(box, style, true); - } else { - if (useTextAsLabel) { - return createTextLabel(box, style); - } else { - return createLabel(box, style); - } - } - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle() | SWT.WRAP); - lbl.setLayoutData(CmsSwtUtils.fillWidth()); - if (style != null) - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - protected Text createTextLabel(Composite box, String style) { - Text lbl = new Text(box, getStyle() | SWT.MULTI); - lbl.setEditable(false); - lbl.setLayoutData(CmsSwtUtils.fillWidth()); - if (style != null) - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - protected Text createText(Composite box, String style, boolean editable) { - highlight = new Composite(box, SWT.NONE); - highlight.setBackground(highlightColor); - GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false); - highlightGd.widthHint = 5; - highlightGd.heightHint = 3; - highlight.setLayoutData(highlightGd); - - final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP); - text.setEditable(editable); - GridData textLayoutData = CmsSwtUtils.fillWidth(); - // textLayoutData.heightHint = preferredHeight; - text.setLayoutData(textLayoutData); - if (style != null) - CmsSwtUtils.style(text, style); - text.setFocus(); - return text; - } - - @Override - protected void clear(boolean deep) { - if (highlight != null) - highlight.dispose(); - super.clear(deep); - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) - ((Label) child).setText(text); - else if (child instanceof Text) - ((Text) child).setText(text); - } - - public Text getAsText() { - return (Text) getControl(); - } - - public Label getAsLabel() { - return (Label) getControl(); - } - - public String getText() { - Control child = getControl(); - - if (child instanceof Label) - return ((Label) child).getText(); - else if (child instanceof Text) - return ((Text) child).getText(); - else - throw new IllegalStateException("Unsupported control " + child.getClass()); - } - - /** @deprecated Use {@link #isEditable()} instead. */ - @Deprecated - public boolean getEditable() { - return isEditable(); - } - - public boolean isEditable() { - return editable; - } - - public void setUseTextAsLabel(boolean useTextAsLabel) { - this.useTextAsLabel = useTextAsLabel; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java deleted file mode 100644 index 3a4a60c9f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.internal.JcrFileUploadReceiver; -import org.argeo.cms.ui.viewers.NodePart; -import org.argeo.cms.ui.viewers.Section; -import org.argeo.cms.ui.viewers.SectionPart; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.service.ServerPushSession; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** An image within the Argeo Text framework */ -public class Img extends EditableImage implements SectionPart, NodePart { - private static final long serialVersionUID = 6233572783968188476L; - - private final Section section; - - private final CmsImageManager 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 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 imageManager) throws RepositoryException { - super(parent, swtStyle, imgNode, false, preferredImageSize); - this.section = section; - this.imageManager = imageManager != null ? imageManager - : (CmsImageManager) 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 getImageManager() { - return imageManager; - } - - protected Control createImageChooser(Composite box, String style) throws RepositoryException { - JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(), - imageManager); - if (currentUploadHandler != null) - currentUploadHandler.dispose(); - currentUploadHandler = prepareUpload(receiver); - final ServerPushSession pushSession = new ServerPushSession(); - final FileUpload fileUpload = new FileUpload(box, SWT.NONE); - CmsSwtUtils.style(fileUpload, style); - fileUpload.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -9158471843941668562L; - - @Override - public void widgetSelected(SelectionEvent e) { - pushSession.start(); - fileUpload.submit(currentUploadHandler.getUploadUrl()); - } - }); - return fileUpload; - } - - protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) { - final FileUploadHandler uploadHandler = new FileUploadHandler(receiver); - if (fileUploadListener != null) - uploadHandler.addUploadListener(fileUploadListener); - return uploadHandler; - } - - @Override - public Section getSection() { - return section; - } - - public void setFileUploadListener(FileUploadListener fileUploadListener) { - this.fileUploadListener = fileUploadListener; - if (currentUploadHandler != null) - currentUploadHandler.addUploadListener(fileUploadListener); - } - - @Override - public Node getItem() throws RepositoryException { - return getNode(); - } - - @Override - public String getPartId() { - return getNodeId(); - } - - @Override - public String toString() { - return "Img #" + getPartId(); - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java deleted file mode 100644 index 5d3576f27..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Item; -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.jcr.JcrException; -import org.eclipse.swt.widgets.Composite; - -/** A composite which can (optionally) manage a JCR Item. */ -public class JcrComposite extends Composite { - private static final long serialVersionUID = -1447009015451153367L; - - private Session session; - - private String nodeId; - private String property = null; - private Node cache; - - /** Regular composite constructor. No layout is set. */ - public JcrComposite(Composite parent, int style) { - super(parent, style); - session = null; - nodeId = null; - } - - public JcrComposite(Composite parent, int style, Item item) { - this(parent, style, item, false); - } - - public JcrComposite(Composite parent, int style, Item item, boolean cacheImmediately) { - super(parent, style); - if (item != null) - try { - this.session = item.getSession(); -// if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) { -// // (useless?) optimization: we only save a pointer to the session, -// // not even a reference to the item -// this.nodeId = null; -// } else { - Node node; - Property property = null; - if (item instanceof Node) { - node = (Node) item; - } else {// Property - property = (Property) item; - if (property.isMultiple())// TODO manage property index - throw new UnsupportedOperationException("Multiple properties not supported yet."); - this.property = property.getName(); - node = property.getParent(); - } - this.nodeId = node.getIdentifier(); - if (cacheImmediately) - this.cache = node; -// } - setLayout(CmsSwtUtils.noSpaceGridLayout()); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create composite from " + item, e); - } - } - - public synchronized Node getNode() { - try { - if (!itemIsNode()) - throw new IllegalStateException("Item is not a Node"); - return getNodeInternal(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get node " + nodeId, e); - } - } - - private synchronized Node getNodeInternal() throws RepositoryException { - if (cache != null) - return cache; - else if (session != null) - if (nodeId != null) - return session.getNodeByIdentifier(nodeId); - else - return null; - else - return null; - } - - public synchronized String getPropertyName() { - try { - return getProperty().getName(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property name", e); - } - } - - public synchronized Node getPropertyNode() { - try { - return getProperty().getNode(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property name", e); - } - } - - public synchronized Property getProperty() { - try { - if (itemIsNode()) - throw new IllegalStateException("Item is not a Property"); - Node node = getNodeInternal(); - if (!node.hasProperty(property)) - throw new IllegalStateException("Property " + property + " is not set on " + node); - return node.getProperty(property); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property " + property + " from node " + nodeId, e); - } - } - - public synchronized boolean itemIsNode() { - return property == null; - } - - public synchronized boolean itemExists() { - if (session == null) - return false; - try { - Node n = session.getNodeByIdentifier(nodeId); - if (!itemIsNode()) - return n.hasProperty(property); - else - return true; - } catch (ItemNotFoundException e) { - return false; - } catch (RepositoryException e) { - throw new JcrException("Cannot check whether node exists", e); - } - } - - /** Set/update the cache or change the node */ - public synchronized void setNode(Node node) { - if (!itemIsNode()) - throw new IllegalArgumentException("Cannot set a Node on a Property"); - - if (node == null) {// clear cache - this.cache = null; - return; - } - - try { -// if (session != null || session != node.getSession())// check session -// throw new IllegalArgumentException("Uncompatible session"); -// if (session == null) - session = node.getSession(); - if (nodeId == null || !nodeId.equals(node.getIdentifier())) { - nodeId = node.getIdentifier(); - cache = node; - itemUpdated(); - } else { - cache = node;// set/update cache - } - } catch (RepositoryException e) { - throw new IllegalStateException(e); - } - } - - /** Set/update the cache or change the property */ - public synchronized void setProperty(Property prop) { - if (itemIsNode()) - throw new IllegalArgumentException("Cannot set a Property on a Node"); - - if (prop == null) {// clear cache - this.cache = null; - return; - } - - try { - if (session == null || session != prop.getSession())// check session - throw new IllegalArgumentException("Uncompatible session"); - - Node node = prop.getNode(); - if (nodeId == null || !nodeId.equals(node.getIdentifier()) || !property.equals(prop.getName())) { - nodeId = node.getIdentifier(); - property = prop.getName(); - cache = node; - itemUpdated(); - } else { - cache = node;// set/update cache - } - } catch (RepositoryException e) { - throw new IllegalStateException(e); - } - } - - public synchronized String getNodeId() { - return nodeId; - } - - /** Change the node, does nothing if same. */ - public synchronized void setNodeId(String nodeId) throws RepositoryException { - if (this.nodeId != null && this.nodeId.equals(nodeId)) - return; - this.nodeId = nodeId; - if (cache != null) - cache = session.getNodeByIdentifier(this.nodeId); - itemUpdated(); - } - - protected synchronized void itemUpdated() { - layout(); - } - - public Session getSession() { - return session; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java deleted file mode 100644 index 517e796e9..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** - * A composite that can be scrolled vertically. It wraps a - * {@link ScrolledComposite} (and is being wrapped by it), simplifying its - * configuration. - */ -public class ScrolledPage extends Composite { - private static final long serialVersionUID = 1593536965663574437L; - - private ScrolledComposite scrolledComposite; - - public ScrolledPage(Composite parent, int style) { - this(parent, style, false); - } - - public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) { - super(createScrolledComposite(parent, alwaysShowScroll), style); - scrolledComposite = (ScrolledComposite) getParent(); - scrolledComposite.setContent(this); - - scrolledComposite.setExpandVertical(true); - scrolledComposite.setExpandHorizontal(true); - scrolledComposite.addControlListener(new ScrollControlListener()); - } - - private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) { - ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL); - scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll); - return scrolledComposite; - } - - @Override - public void layout(boolean changed, boolean all) { - updateScroll(); - super.layout(changed, all); - } - - public void showControl(Control control) { - scrolledComposite.showControl(control); - } - - protected void updateScroll() { - Rectangle r = scrolledComposite.getClientArea(); - Point preferredSize = computeSize(r.width, SWT.DEFAULT); - scrolledComposite.setMinHeight(preferredSize.y); - } - - // public ScrolledComposite getScrolledComposite() { - // return this.scrolledComposite; - // } - - /** Set it on the wrapping scrolled composite */ - @Override - public void setLayoutData(Object layoutData) { - scrolledComposite.setLayoutData(layoutData); - } - - private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter { - private static final long serialVersionUID = -3586986238567483316L; - - public void controlResized(ControlEvent e) { - updateScroll(); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java deleted file mode 100644 index e3a5cb473..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Item; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Editable text part displaying styled text. */ -public abstract class StyledControl extends JcrComposite implements CmsUiConstants { - private static final long serialVersionUID = -6372283442330912755L; - private Control control; - - private Composite container; - private Composite box; - - protected MouseListener mouseListener; - protected FocusListener focusListener; - - private Boolean editing = Boolean.FALSE; - - private Composite ancestorToLayout; - - public StyledControl(Composite parent, int swtStyle) { - super(parent, swtStyle); - setLayout(CmsSwtUtils.noSpaceGridLayout()); - } - - public StyledControl(Composite parent, int style, Item item) { - super(parent, style, item); - } - - public StyledControl(Composite parent, int style, Item item, boolean cacheImmediately) { - super(parent, style, item, cacheImmediately); - } - - protected abstract Control createControl(Composite box, String style); - - protected Composite createBox() { - Composite box = new Composite(container, SWT.INHERIT_DEFAULT); - setContainerLayoutData(box); - box.setLayout(CmsSwtUtils.noSpaceGridLayout(3)); - return box; - } - - protected Composite createContainer() { - Composite container = new Composite(this, SWT.INHERIT_DEFAULT); - setContainerLayoutData(container); - container.setLayout(CmsSwtUtils.noSpaceGridLayout()); - return container; - } - - public Control getControl() { - return control; - } - - protected synchronized Boolean isEditing() { - return editing; - } - - public synchronized void startEditing() { - assert !isEditing(); - editing = true; - // int height = control.getSize().y; - String style = (String) EclipseUiSpecificUtils.getStyleData(control); - clear(false); - refreshControl(style); - - // add the focus listener to the newly created edition control - if (focusListener != null) - control.addFocusListener(focusListener); - } - - public synchronized void stopEditing() { - assert isEditing(); - editing = false; - String style = (String) EclipseUiSpecificUtils.getStyleData(control); - clear(false); - refreshControl(style); - } - - protected void refreshControl(String style) { - control = createControl(box, style); - setControlLayoutData(control); - if (ancestorToLayout != null) - ancestorToLayout.layout(true, true); - else - getParent().layout(true, true); - } - - public void setStyle(String style) { - Object currentStyle = null; - if (control != null) - currentStyle = EclipseUiSpecificUtils.getStyleData(control); - if (currentStyle != null && currentStyle.equals(style)) - return; - - clear(true); - refreshControl(style); - - if (style != null) { - CmsSwtUtils.style(box, style + "_box"); - CmsSwtUtils.style(container, style + "_container"); - } - } - - /** To be overridden */ - protected void setControlLayoutData(Control control) { - control.setLayoutData(CmsSwtUtils.fillWidth()); - } - - /** To be overridden */ - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsSwtUtils.fillWidth()); - } - - protected void clear(boolean deep) { - if (deep) { - for (Control control : getChildren()) - control.dispose(); - container = createContainer(); - box = createBox(); - } else { - control.dispose(); - } - } - - public void setMouseListener(MouseListener mouseListener) { - if (this.mouseListener != null && control != null) - control.removeMouseListener(this.mouseListener); - this.mouseListener = mouseListener; - if (control != null && this.mouseListener != null) - control.addMouseListener(mouseListener); - } - - public void setFocusListener(FocusListener focusListener) { - if (this.focusListener != null && control != null) - control.removeFocusListener(this.focusListener); - this.focusListener = focusListener; - if (control != null && this.focusListener != null) - control.addFocusListener(focusListener); - } - - public void setAncestorToLayout(Composite ancestorToLayout) { - this.ancestorToLayout = ancestorToLayout; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java deleted file mode 100644 index e461ed0df..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.ui.widgets; - -/** Styles references in the CSS. */ -public interface TextStyles { - /** The whole page area */ - public final static String TEXT_AREA = "text_area"; - /** Area providing controls for editing text */ - public final static String TEXT_EDITOR_HEADER = "text_editor_header"; - /** The styled composite for editing the text */ - public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite"; - /** A section */ - public final static String TEXT_SECTION = "text_section"; - /** A paragraph */ - public final static String TEXT_PARAGRAPH = "text_paragraph"; - /** An image */ - public final static String TEXT_IMG = "text_img"; - /** The dialog to edit styled paragraph */ - public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog"; - - /* - * DEFAULT TEXT STYLES - */ - /** Default style for text body */ - public final static String TEXT_DEFAULT = "text_default"; - /** Fixed-width, typically code */ - public final static String TEXT_PRE = "text_pre"; - /** Quote */ - public final static String TEXT_QUOTE = "text_quote"; - /** Title */ - public final static String TEXT_TITLE = "text_title"; - /** Header (to be dynamically completed with the depth, e.g. text_h1) */ - public final static String TEXT_H = "text_h"; - - /** Default style for images */ - public final static String TEXT_IMAGE = "text_image"; - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java deleted file mode 100644 index 514f75380..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS generic widgets, based on SWT. */ -package org.argeo.cms.ui.widgets; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java deleted file mode 100644 index fdafa982e..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.AbstractTreeContentProvider; -import org.argeo.eclipse.ui.EclipseUiException; - -/** Canonical implementation of tree content provider manipulating JCR nodes. */ -public abstract class AbstractNodeContentProvider extends - AbstractTreeContentProvider { - private static final long serialVersionUID = -4905836490027272569L; - - private final static CmsLog log = CmsLog - .getLog(AbstractNodeContentProvider.class); - - private Session session; - - public AbstractNodeContentProvider(Session session) { - this.session = session; - } - - /** - * Whether this path is a base path (and thus has no parent). By default it - * returns true if path is '/' (root node) - */ - protected Boolean isBasePath(String path) { - // root node - return path.equals("/"); - } - - @Override - public Object[] getChildren(Object element) { - Object[] children; - if (element instanceof Node) { - try { - Node node = (Node) element; - children = getChildren(node); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get children of " + element, e); - } - } else if (element instanceof WrappedNode) { - WrappedNode wrappedNode = (WrappedNode) element; - try { - children = getChildren(wrappedNode.getNode()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get children of " - + wrappedNode, e); - } - } else if (element instanceof NodesWrapper) { - NodesWrapper node = (NodesWrapper) element; - children = node.getChildren(); - } else { - children = super.getChildren(element); - } - - children = sort(element, children); - return children; - } - - /** Do not sort by default. To be overidden to provide custom sort. */ - protected Object[] sort(Object parent, Object[] children) { - return children; - } - - /** - * To be overridden in order to filter out some nodes. Does nothing by - * default. The provided list is a temporary one and can thus be modified - * directly . (e.g. via an iterator) - */ - protected List filterChildren(List children) - throws RepositoryException { - return children; - } - - protected Object[] getChildren(Node node) throws RepositoryException { - List nodes = new ArrayList(); - for (NodeIterator nit = node.getNodes(); nit.hasNext();) - nodes.add(nit.nextNode()); - nodes = filterChildren(nodes); - return nodes.toArray(); - } - - @Override - public Object getParent(Object element) { - if (element instanceof Node) { - Node node = (Node) element; - try { - String path = node.getPath(); - if (isBasePath(path)) - return null; - else - return node.getParent(); - } catch (RepositoryException e) { - log.warn("Cannot get parent of " + element + ": " + e); - return null; - } - } else if (element instanceof WrappedNode) { - WrappedNode wrappedNode = (WrappedNode) element; - return wrappedNode.getParent(); - } else if (element instanceof NodesWrapper) { - NodesWrapper nodesWrapper = (NodesWrapper) element; - return this.getParent(nodesWrapper.getNode()); - } - return super.getParent(element); - } - - @Override - public boolean hasChildren(Object element) { - try { - if (element instanceof Node) { - Node node = (Node) element; - return node.hasNodes(); - } else if (element instanceof WrappedNode) { - WrappedNode wrappedNode = (WrappedNode) element; - return wrappedNode.getNode().hasNodes(); - } else if (element instanceof NodesWrapper) { - NodesWrapper nodesWrapper = (NodesWrapper) element; - return nodesWrapper.hasChildren(); - } - - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check whether " + element - + " has children", e); - } - return super.hasChildren(element); - } - - public Session getSession() { - return session; - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java deleted file mode 100644 index b880a63c5..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.RepositoryException; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -import javax.jcr.observation.EventListener; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.widgets.Display; - -/** - * {@link EventListener} which simplifies running actions within the UI thread. - */ -public abstract class AsyncUiEventListener implements EventListener { - // private final static Log logSuper = LogFactory - // .getLog(AsyncUiEventListener.class); - private final CmsLog logThis = CmsLog.getLog(getClass()); - - private final Display display; - - public AsyncUiEventListener(Display display) { - super(); - this.display = display; - } - - /** Called asynchronously in the UI thread. */ - protected abstract void onEventInUiThread(List events) throws RepositoryException; - - /** - * Whether these events should be processed in the UI or skipped with no UI - * job created. - */ - protected Boolean willProcessInUiThread(List events) throws RepositoryException { - return true; - } - - protected CmsLog getLog() { - return logThis; - } - - public final void onEvent(final EventIterator eventIterator) { - final List events = new ArrayList(); - while (eventIterator.hasNext()) - events.add(eventIterator.nextEvent()); - - if (logThis.isTraceEnabled()) - logThis.trace("Received " + events.size() + " events"); - - try { - if (!willProcessInUiThread(events)) - return; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot test skip events " + events, e); - } - - // Job job = new Job("JCR Events") { - // protected IStatus run(IProgressMonitor monitor) { - // if (display.isDisposed()) { - // logSuper.warn("Display is disposed cannot update UI"); - // return Status.CANCEL_STATUS; - // } - - if (!display.isDisposed()) - display.asyncExec(new Runnable() { - public void run() { - try { - onEventInUiThread(events); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot process events " + events, e); - } - } - }); - - // return Status.OK_STATUS; - // } - // }; - // job.schedule(); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java deleted file mode 100644 index 22ffeafc6..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** - * Default label provider to manage node and corresponding UI objects. It - * provides reasonable overwrite-able default for known JCR types. - */ -public class DefaultNodeLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = 1216182332792151235L; - - public String getText(Object element) { - try { - if (element instanceof Node) { - return getText((Node) element); - } else if (element instanceof WrappedNode) { - return getText(((WrappedNode) element).getNode()); - } else if (element instanceof NodesWrapper) { - return getText(((NodesWrapper) element).getNode()); - } - return super.getText(element); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get text for of " + element, e); - } - } - - protected String getText(Node node) throws RepositoryException { - if (node.isNodeType(NodeType.MIX_TITLE) - && node.hasProperty(Property.JCR_TITLE)) - return node.getProperty(Property.JCR_TITLE).getString(); - else - return node.getName(); - } - - @Override - public Image getImage(Object element) { - try { - if (element instanceof Node) { - return getImage((Node) element); - } else if (element instanceof WrappedNode) { - return getImage(((WrappedNode) element).getNode()); - } else if (element instanceof NodesWrapper) { - return getImage(((NodesWrapper) element).getNode()); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot retrieve image for " + element, e); - } - return super.getImage(element); - } - - protected Image getImage(Node node) throws RepositoryException { - // FIXME who uses that? - return null; - } - - @Override - public String getToolTipText(Object element) { - try { - if (element instanceof Node) { - return getToolTipText((Node) element); - } else if (element instanceof WrappedNode) { - return getToolTipText(((WrappedNode) element).getNode()); - } else if (element instanceof NodesWrapper) { - return getToolTipText(((NodesWrapper) element).getNode()); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get tooltip for " + element, e); - } - return super.getToolTipText(element); - } - - protected String getToolTipText(Node node) throws RepositoryException { - return null; - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java deleted file mode 100644 index b83aaa25a..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import org.argeo.jcr.JcrMonitor; -import org.eclipse.core.runtime.IProgressMonitor; - -/** - * Wraps an Eclipse {@link IProgressMonitor} so that it can be passed to - * framework agnostic Argeo routines. - */ -public class EclipseJcrMonitor implements JcrMonitor { - private final IProgressMonitor progressMonitor; - - public EclipseJcrMonitor(IProgressMonitor progressMonitor) { - this.progressMonitor = progressMonitor; - } - - public void beginTask(String name, int totalWork) { - progressMonitor.beginTask(name, totalWork); - } - - public void done() { - progressMonitor.done(); - } - - public boolean isCanceled() { - return progressMonitor.isCanceled(); - } - - public void setCanceled(boolean value) { - progressMonitor.setCanceled(value); - } - - public void setTaskName(String name) { - progressMonitor.setTaskName(name); - } - - public void subTask(String name) { - progressMonitor.subTask(name); - } - - public void worked(int work) { - progressMonitor.worked(work); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java deleted file mode 100644 index 420154b83..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.Calendar; - -import javax.jcr.Node; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.jcr.lists.NodeViewerComparator; -import org.argeo.eclipse.ui.jcr.lists.RowViewerComparator; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Table; - -/** Utility methods to simplify UI development using SWT (or RWT), jface and JCR. */ -public class JcrUiUtils { - - /** - * Centralizes management of updating property value. Among other to avoid - * infinite loop when the new value is the same as the ones that is already - * stored in JCR. - * - * @return true if the value as changed - */ - public static boolean setJcrProperty(Node node, String propName, - int propertyType, Object value) { - try { - switch (propertyType) { - case PropertyType.STRING: - if ("".equals((String) value) - && (!node.hasProperty(propName) || node - .hasProperty(propName) - && "".equals(node.getProperty(propName) - .getString()))) - // workaround the fact that the Text widget value cannot be - // set to null - return false; - else if (node.hasProperty(propName) - && node.getProperty(propName).getString() - .equals((String) value)) - // nothing changed yet - return false; - else { - node.setProperty(propName, (String) value); - return true; - } - case PropertyType.BOOLEAN: - if (node.hasProperty(propName) - && node.getProperty(propName).getBoolean() == (Boolean) value) - // nothing changed yet - return false; - else { - node.setProperty(propName, (Boolean) value); - return true; - } - case PropertyType.DATE: - if (node.hasProperty(propName) - && node.getProperty(propName).getDate() - .equals((Calendar) value)) - // nothing changed yet - return false; - else { - node.setProperty(propName, (Calendar) value); - return true; - } - case PropertyType.LONG: - Long lgValue = (Long) value; - - if (lgValue == null) - lgValue = 0L; - - if (node.hasProperty(propName) - && node.getProperty(propName).getLong() == lgValue) - // nothing changed yet - return false; - else { - node.setProperty(propName, lgValue); - return true; - } - - default: - throw new EclipseUiException("Unimplemented property save"); - } - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while setting property", - re); - } - } - - /** - * Creates a new selection adapter in order to provide sorting abitily on a - * SWT Table that display a row list - **/ - public static SelectionAdapter getRowSelectionAdapter(final int index, - final int propertyType, final String selectorName, - final String propertyName, final RowViewerComparator comparator, - final TableViewer viewer) { - SelectionAdapter selectionAdapter = new SelectionAdapter() { - private static final long serialVersionUID = -5738918304901437720L; - - @Override - public void widgetSelected(SelectionEvent e) { - Table table = viewer.getTable(); - comparator.setColumn(propertyType, selectorName, propertyName); - int dir = table.getSortDirection(); - if (table.getSortColumn() == table.getColumn(index)) { - dir = dir == SWT.UP ? SWT.DOWN : SWT.UP; - } else { - dir = SWT.DOWN; - } - table.setSortDirection(dir); - table.setSortColumn(table.getColumn(index)); - viewer.refresh(); - } - }; - return selectionAdapter; - } - - /** - * Creates a new selection adapter in order to provide sorting abitily on a - * swt table that display a row list - **/ - public static SelectionAdapter getNodeSelectionAdapter(final int index, - final int propertyType, final String propertyName, - final NodeViewerComparator comparator, final TableViewer viewer) { - SelectionAdapter selectionAdapter = new SelectionAdapter() { - private static final long serialVersionUID = -1683220869195484625L; - - @Override - public void widgetSelected(SelectionEvent e) { - Table table = viewer.getTable(); - comparator.setColumn(propertyType, propertyName); - int dir = table.getSortDirection(); - if (table.getSortColumn() == table.getColumn(index)) { - dir = dir == SWT.UP ? SWT.DOWN : SWT.UP; - } else { - dir = SWT.DOWN; - } - table.setSortDirection(dir); - table.setSortColumn(table.getColumn(index)); - viewer.refresh(); - } - }; - return selectionAdapter; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java deleted file mode 100644 index 7e12becd8..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; - -/** Simplifies writing JCR-based column label provider. */ -public class NodeColumnLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -6586692836928505358L; - - protected String getNodeText(Node node) throws RepositoryException { - return super.getText(node); - } - - protected String getNodeToolTipText(Node node) throws RepositoryException { - return super.getToolTipText(node); - } - - protected Image getNodeImage(Node node) throws RepositoryException { - return super.getImage(node); - } - - protected Font getNodeFont(Node node) throws RepositoryException { - return super.getFont(node); - } - - public Color getNodeBackground(Node node) throws RepositoryException { - return super.getBackground(node); - } - - public Color getNodeForeground(Node node) throws RepositoryException { - return super.getForeground(node); - } - - @Override - public String getText(Object element) { - try { - if (element instanceof Node) - return getNodeText((Node) element); - else if (element instanceof NodeElement) - return getNodeText(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Image getImage(Object element) { - try { - if (element instanceof Node) - return getNodeImage((Node) element); - else if (element instanceof NodeElement) - return getNodeImage(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public String getToolTipText(Object element) { - try { - if (element instanceof Node) - return getNodeToolTipText((Node) element); - else if (element instanceof NodeElement) - return getNodeToolTipText(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Font getFont(Object element) { - try { - if (element instanceof Node) - return getNodeFont((Node) element); - else if (element instanceof NodeElement) - return getNodeFont(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Color getBackground(Object element) { - try { - if (element instanceof Node) - return getNodeBackground((Node) element); - else if (element instanceof NodeElement) - return getNodeBackground(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Color getForeground(Object element) { - try { - if (element instanceof Node) - return getNodeForeground((Node) element); - else if (element instanceof NodeElement) - return getNodeForeground(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java deleted file mode 100644 index 787c92ed5..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; - -/** An element which is related to a JCR {@link Node}. */ -public interface NodeElement { - Node getNode(); -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java deleted file mode 100644 index 2f3d64d6b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.IElementComparer; - -/** Element comparer for JCR node, to be used in JFace viewers. */ -public class NodeElementComparer implements IElementComparer { - - public boolean equals(Object a, Object b) { - try { - if ((a instanceof Node) && (b instanceof Node)) { - Node nodeA = (Node) a; - Node nodeB = (Node) b; - return nodeA.getIdentifier().equals(nodeB.getIdentifier()); - } else { - return a.equals(b); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot compare nodes", e); - } - } - - public int hashCode(Object element) { - try { - if (element instanceof Node) - return ((Node) element).getIdentifier().hashCode(); - return element.hashCode(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get hash code", e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java deleted file mode 100644 index 2f808a51f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java +++ /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 getWrappedNodes() throws RepositoryException { - List nodes = new ArrayList(); - for (NodeIterator nit = getNodeIterator(); nit.hasNext();) - nodes.add(new WrappedNode(this, nit.nextNode())); - return nodes; - } - - public Object[] getChildren() { - try { - return getWrappedNodes().toArray(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get wrapped children", e); - } - } - - /** - * @return true by default because we don't want to compute the wrapped - * nodes twice - */ - public Boolean hasChildren() { - return true; - } - - public Node getNode() { - return node; - } - - @Override - public int hashCode() { - return node.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof NodesWrapper) - return node.equals(((NodesWrapper) obj).getNode()); - else - return false; - } - - public String toString() { - return "nodes wrapper based on " + node; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java deleted file mode 100644 index 934fa6781..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.query.Query; - -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** Content provider based on a JCR {@link Query}. */ -public class QueryTableContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = 760371460907204722L; - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - @Override - public Object[] getElements(Object inputElement) { - Query query = (Query) inputElement; - try { - NodeIterator nit = query.execute().getNodes(); - return JcrUtils.nodeIteratorToList(nit).toArray(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java deleted file mode 100644 index 70c71efea..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.RepositoryException; -import javax.jcr.query.Row; - -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; - -/** Simplifies writing JCR-based column label provider. */ -public class RowColumnLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -6586692836928505358L; - - protected String getRowText(Row row) throws RepositoryException { - return super.getText(row); - } - - protected String getRowToolTipText(Row row) throws RepositoryException { - return super.getToolTipText(row); - } - - protected Image getRowImage(Row row) throws RepositoryException { - return super.getImage(row); - } - - protected Font getRowFont(Row row) throws RepositoryException { - return super.getFont(row); - } - - public Color getRowBackground(Row row) throws RepositoryException { - return super.getBackground(row); - } - - public Color getRowForeground(Row row) throws RepositoryException { - return super.getForeground(row); - } - - @Override - public String getText(Object element) { - try { - if (element instanceof Row) - return getRowText((Row) element); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Image getImage(Object element) { - try { - if (element instanceof Row) - return getRowImage((Row) element); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public String getToolTipText(Object element) { - try { - if (element instanceof Row) - return getRowToolTipText((Row) element); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Font getFont(Object element) { - try { - if (element instanceof Row) - return getRowFont((Row) element); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Color getBackground(Object element) { - try { - if (element instanceof Row) - return getRowBackground((Row) element); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Color getForeground(Object element) { - try { - if (element instanceof Row) - return getRowForeground((Row) element); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java deleted file mode 100644 index cb235d76b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java +++ /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 basePaths; - private Boolean mkdirs = false; - - public SimpleNodeContentProvider(Session session, String... basePaths) { - this(session, Arrays.asList(basePaths)); - } - - public SimpleNodeContentProvider(Session session, List 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 baseNodes = new ArrayList(); - 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 getBasePaths() { - return basePaths; - } - - public void setMkdirs(Boolean mkdirs) { - this.mkdirs = mkdirs; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java deleted file mode 100644 index 1ce315429..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.version.Version; - -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** Simplifies writing JCR-based column label provider. */ -public class VersionColumnLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -6117690082313161159L; - - protected String getVersionText(Version version) throws RepositoryException { - return super.getText(version); - } - - protected String getVersionToolTipText(Version version) throws RepositoryException { - return super.getToolTipText(version); - } - - protected Image getVersionImage(Version version) throws RepositoryException { - return super.getImage(version); - } - - protected String getUserName(Version version) throws RepositoryException { - Node node = version.getFrozenNode(); - if(node.hasProperty(Property.JCR_LAST_MODIFIED_BY)) - return node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString(); - if(node.hasProperty(Property.JCR_CREATED_BY)) - return node.getProperty(Property.JCR_CREATED_BY).getString(); - return null; - } - -// protected String getActivityTitle(Version version) throws RepositoryException { -// Node activity = getActivity(version); -// if (activity == null) -// return null; -// if (activity.hasProperty("jcr:activityTitle")) -// return activity.getProperty("jcr:activityTitle").getString(); -// else -// return activity.getName(); -// } -// -// protected Node getActivity(Version version) throws RepositoryException { -// if (version.hasProperty(Property.JCR_ACTIVITY)) { -// return version.getProperty(Property.JCR_ACTIVITY).getNode(); -// } else -// return null; -// } - - @Override - public String getText(Object element) { - try { - return getVersionText((Version) element); - } catch (RepositoryException e) { - throw new RuntimeException("Runtime repository exception when accessing " + element, e); - } - } - - @Override - public Image getImage(Object element) { - try { - return getVersionImage((Version) element); - } catch (RepositoryException e) { - throw new RuntimeException("Runtime repository exception when accessing " + element, e); - } - } - - @Override - public String getToolTipText(Object element) { - try { - return getVersionToolTipText((Version) element); - } catch (RepositoryException e) { - throw new RuntimeException("Runtime repository exception when accessing " + element, e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java deleted file mode 100644 index 32e5d30c1..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.version.VersionHistory; - -import org.argeo.jcr.Jcr; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** Content provider based on a {@link VersionHistory}. */ -public class VersionHistoryContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -4921107883428887012L; - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - @Override - public Object[] getElements(Object inputElement) { - VersionHistory versionHistory = (VersionHistory) inputElement; - return Jcr.getLinearVersions(versionHistory).toArray(); - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java deleted file mode 100644 index 43df1fe01..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; - -/** Wrap a node (created from a {@link NodesWrapper}) */ -public class WrappedNode { - private final NodesWrapper parent; - private final Node node; - - public WrappedNode(NodesWrapper parent, Node node) { - super(); - this.parent = parent; - this.node = node; - } - - public NodesWrapper getParent() { - return parent; - } - - public Node getNode() { - return node; - } - - public String toString() { - return "wrapped " + node; - } - - @Override - public int hashCode() { - return node.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof WrappedNode) - return node.equals(((WrappedNode) obj).getNode()); - else - return false; - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java deleted file mode 100644 index c5dd73317..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import javax.jcr.Node; -import javax.jcr.query.Row; - -import org.argeo.eclipse.ui.ColumnDefinition; - -/** - * Utility object to manage column in various tables and extracts displaying - * data from JCR - */ -public class JcrColumnDefinition extends ColumnDefinition { - private final static int DEFAULT_COLUMN_SIZE = 120; - - private String selectorName; - private String propertyName; - private int propertyType; - private int columnSize; - - /** - * Use this kind of columns to configure a table that displays JCR - * {@link Row} - * - * @param selectorName - * @param propertyName - * @param propertyType - * @param headerLabel - */ - public JcrColumnDefinition(String selectorName, String propertyName, - int propertyType, String headerLabel) { - super(new SimpleJcrRowLabelProvider(selectorName, propertyName), - headerLabel); - this.selectorName = selectorName; - this.propertyName = propertyName; - this.propertyType = propertyType; - this.columnSize = DEFAULT_COLUMN_SIZE; - } - - /** - * Use this kind of columns to configure a table that displays JCR - * {@link Row} - * - * @param selectorName - * @param propertyName - * @param propertyType - * @param headerLabel - * @param columnSize - */ - public JcrColumnDefinition(String selectorName, String propertyName, - int propertyType, String headerLabel, int columnSize) { - super(new SimpleJcrRowLabelProvider(selectorName, propertyName), - headerLabel, columnSize); - this.selectorName = selectorName; - this.propertyName = propertyName; - this.propertyType = propertyType; - this.columnSize = columnSize; - } - - /** - * Use this kind of columns to configure a table that displays JCR - * {@link Node} - * - * @param propertyName - * @param propertyType - * @param headerLabel - * @param columnSize - */ - public JcrColumnDefinition(String propertyName, int propertyType, - String headerLabel, int columnSize) { - super(new SimpleJcrNodeLabelProvider(propertyName), headerLabel, - columnSize); - this.propertyName = propertyName; - this.propertyType = propertyType; - this.columnSize = columnSize; - } - - public String getSelectorName() { - return selectorName; - } - - public void setSelectorName(String selectorName) { - this.selectorName = selectorName; - } - - public String getPropertyName() { - return propertyName; - } - - public void setPropertyName(String propertyName) { - this.propertyName = propertyName; - } - - public int getPropertyType() { - return propertyType; - } - - public void setPropertyType(int propertyType) { - this.propertyType = propertyType; - } - - public int getColumnSize() { - return columnSize; - } - - public void setColumnSize(int columnSize) { - this.columnSize = columnSize; - } - - public String getHeaderLabel() { - return super.getLabel(); - } - - public void setHeaderLabel(String headerLabel) { - super.setLabel(headerLabel); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java deleted file mode 100644 index 341b3abee..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java +++ /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: - * // IMPORTANT: initialize comparator before setting it - * JcrColumnDefinition firstCol = colDefs.get(0); - * comparator.setColumn(firstCol.getPropertyType(), - * firstCol.getPropertyName()); - * viewer.setComparator(comparator); - */ -public class NodeViewerComparator extends ViewerComparator { - private static final long serialVersionUID = -7782916140737279027L; - - protected String propertyName; - - protected int propertyType; - public static final int ASCENDING = 0, DESCENDING = 1; - protected int direction = DESCENDING; - - public NodeViewerComparator() { - } - - /** - * e1 and e2 must both be Jcr nodes. - * - * @param viewer - * @param e1 - * @param e2 - * @return - */ - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - int rc = 0; - long lc = 0; - - try { - Node n1 = (Node) e1; - Node n2 = (Node) e2; - - Value v1 = null; - Value v2 = null; - if (n1.hasProperty(propertyName)) - v1 = n1.getProperty(propertyName).getValue(); - if (n2.hasProperty(propertyName)) - v2 = n2.getProperty(propertyName).getValue(); - - if (v2 == null && v1 == null) - return 0; - else if (v2 == null) - return -1; - else if (v1 == null) - return 1; - - switch (propertyType) { - case PropertyType.STRING: - rc = v1.getString().compareTo(v2.getString()); - break; - case PropertyType.BOOLEAN: - boolean b1 = v1.getBoolean(); - boolean b2 = v2.getBoolean(); - if (b1 == b2) - rc = 0; - else - // we assume true is greater than false - rc = b1 ? 1 : -1; - break; - case PropertyType.DATE: - Calendar c1 = v1.getDate(); - Calendar c2 = v2.getDate(); - if (c1 == null || c2 == null) - // log.trace("undefined date"); - ; - lc = c1.getTimeInMillis() - c2.getTimeInMillis(); - if (lc < Integer.MIN_VALUE) - rc = -1; - else if (lc > Integer.MAX_VALUE) - rc = 1; - else - rc = (int) lc; - break; - case PropertyType.LONG: - long l1; - long l2; - // TODO Sometimes an empty string is set instead of a long - try { - l1 = v1.getLong(); - } catch (ValueFormatException ve) { - l1 = 0; - } - try { - l2 = v2.getLong(); - } catch (ValueFormatException ve) { - l2 = 0; - } - - lc = l1 - l2; - if (lc < Integer.MIN_VALUE) - rc = -1; - else if (lc > Integer.MAX_VALUE) - rc = 1; - else - rc = (int) lc; - break; - case PropertyType.DECIMAL: - BigDecimal bd1 = v1.getDecimal(); - BigDecimal bd2 = v2.getDecimal(); - rc = bd1.compareTo(bd2); - break; - case PropertyType.DOUBLE: - Double d1 = v1.getDouble(); - Double d2 = v2.getDouble(); - rc = d1.compareTo(d2); - break; - default: - throw new EclipseUiException( - "Unimplemented comparaison for PropertyType " - + propertyType); - } - // If descending order, flip the direction - if (direction == DESCENDING) { - rc = -rc; - } - - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error " - + "while comparing nodes", re); - } - return rc; - } - - /** - * @param propertyType - * Corresponding JCR type - * @param propertyName - * name of the property to use. - */ - public void setColumn(int propertyType, String propertyName) { - if (this.propertyName != null && this.propertyName.equals(propertyName)) { - // Same column as last sort; toggle the direction - direction = 1 - direction; - } else { - // New column; do an ascending sort - this.propertyType = propertyType; - this.propertyName = propertyName; - direction = ASCENDING; - } - } - - // Getters and setters - protected String getPropertyName() { - return propertyName; - } - - protected void setPropertyName(String propertyName) { - this.propertyName = propertyName; - } - - protected int getPropertyType() { - return propertyType; - } - - protected void setPropertyType(int propertyType) { - this.propertyType = propertyType; - } - - protected int getDirection() { - return direction; - } - - protected void setDirection(int direction) { - this.direction = direction; - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java deleted file mode 100644 index 455fb0dcd..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.query.Row; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.Viewer; - -/** - * Base comparator to enable ordering on Table or Tree viewer that display Jcr - * rows - */ -public class RowViewerComparator extends NodeViewerComparator { - private static final long serialVersionUID = 7020939505172625113L; - protected String selectorName; - - public RowViewerComparator() { - } - - /** - * e1 and e2 must both be Jcr rows. - * - * @param viewer - * @param e1 - * @param e2 - * @return - */ - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - try { - Node n1 = ((Row) e1).getNode(selectorName); - Node n2 = ((Row) e2).getNode(selectorName); - return super.compare(viewer, n1, n2); - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error " - + "while comparing nodes", re); - } - } - - /** - * @param propertyType - * Corresponding JCR type - * @param propertyName - * name of the property to use. - */ - public void setColumn(int propertyType, String selectorName, - String propertyName) { - if (this.selectorName != null && getPropertyName() != null - && this.selectorName.equals(selectorName) - && this.getPropertyName().equals(propertyName)) { - // Same column as last sort; toggle the direction - setDirection(1 - getDirection()); - } else { - // New column; do a descending sort - setPropertyType(propertyType); - setPropertyName(propertyName); - this.selectorName = selectorName; - setDirection(NodeViewerComparator.ASCENDING); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java deleted file mode 100644 index aa2e3375c..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import java.text.DateFormat; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.SimpleDateFormat; - -import javax.jcr.Node; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** Base implementation of a label provider for controls that display JCR Nodes */ -public class SimpleJcrNodeLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -5215787695436221993L; - - private final static String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy"; - private final static String DEFAULT_NUMBER_FORMAT = "#,##0.0"; - - private DateFormat dateFormat; - private NumberFormat numberFormat; - - final private String propertyName; - - /** - * Default Label provider for a given property of a node. Using default - * pattern for date and number formating - */ - public SimpleJcrNodeLabelProvider(String propertyName) { - this.propertyName = propertyName; - dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT); - numberFormat = DecimalFormat.getInstance(); - ((DecimalFormat) numberFormat).applyPattern(DEFAULT_NUMBER_FORMAT); - } - - /** - * Label provider for a given property of a node optionally precising date - * and/or number format patterns - */ - public SimpleJcrNodeLabelProvider(String propertyName, - String dateFormatPattern, String numberFormatPattern) { - this.propertyName = propertyName; - dateFormat = new SimpleDateFormat( - dateFormatPattern == null ? DEFAULT_DATE_FORMAT - : dateFormatPattern); - numberFormat = DecimalFormat.getInstance(); - ((DecimalFormat) numberFormat) - .applyPattern(numberFormatPattern == null ? DEFAULT_NUMBER_FORMAT - : numberFormatPattern); - } - - @Override - public String getText(Object element) { - try { - Node currNode = (Node) element; - - if (currNode.hasProperty(propertyName)) { - if (currNode.getProperty(propertyName).isMultiple()) { - StringBuilder builder = new StringBuilder(); - for (Value value : currNode.getProperty(propertyName) - .getValues()) { - String currStr = getSingleValueAsString(value); - if (notEmptyString(currStr)) - builder.append(currStr).append("; "); - } - if (builder.length() > 0) - builder.deleteCharAt(builder.length() - 2); - - return builder.toString(); - } else - return getSingleValueAsString(currNode.getProperty( - propertyName).getValue()); - } else - return ""; - } catch (RepositoryException re) { - throw new EclipseUiException("Unable to get text from row", re); - } - } - - private String getSingleValueAsString(Value value) - throws RepositoryException { - switch (value.getType()) { - case PropertyType.STRING: - return value.getString(); - case PropertyType.BOOLEAN: - return "" + value.getBoolean(); - case PropertyType.DATE: - return dateFormat.format(value.getDate().getTime()); - case PropertyType.LONG: - return "" + value.getLong(); - case PropertyType.DECIMAL: - return numberFormat.format(value.getDecimal()); - case PropertyType.DOUBLE: - return numberFormat.format(value.getDouble()); - case PropertyType.NAME: - return value.getString(); - default: - throw new EclipseUiException("Unimplemented label provider " - + "for property type " + value.getType() - + " while getting property " + propertyName + " - value: " - + value.getString()); - - } - } - - private boolean notEmptyString(String string) { - return string != null && !"".equals(string.trim()); - } - - public void setDateFormat(String dateFormatPattern) { - dateFormat = new SimpleDateFormat(dateFormatPattern); - } - - public void setNumberFormat(String numberFormatPattern) { - ((DecimalFormat) numberFormat).applyPattern(numberFormatPattern); - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java deleted file mode 100644 index 5d421f64b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.query.Row; - -import org.argeo.eclipse.ui.EclipseUiException; - -/** - * Base implementation of a label provider for widgets that display JCR Rows. - */ -public class SimpleJcrRowLabelProvider extends SimpleJcrNodeLabelProvider { - private static final long serialVersionUID = -3414654948197181740L; - - final private String selectorName; - - /** - * Default Label provider for a given property of a row. Using default - * pattern for date and number formating - */ - public SimpleJcrRowLabelProvider(String selectorName, String propertyName) { - super(propertyName); - this.selectorName = selectorName; - } - - /** - * Label provider for a given property of a node optionally precising date - * and/or number format patterns - */ - public SimpleJcrRowLabelProvider(String selectorName, String propertyName, - String dateFormatPattern, String numberFormatPattern) { - super(propertyName, dateFormatPattern, numberFormatPattern); - this.selectorName = selectorName; - } - - @Override - public String getText(Object element) { - try { - Row currRow = (Row) element; - Node currNode = currRow.getNode(selectorName); - return super.getText(currNode); - } catch (RepositoryException re) { - throw new EclipseUiException("Unable to get Node " + selectorName - + " from row " + element, re); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java deleted file mode 100644 index 3678aab88..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR utilities for lists. */ -package org.argeo.eclipse.ui.jcr.lists; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java deleted file mode 100644 index 19e3cc3ff..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR utilities. */ -package org.argeo.eclipse.ui.jcr; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java deleted file mode 100644 index c82e666d1..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.argeo.eclipse.ui.jcr.util; - -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.IOUtils; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.FileProvider; - -/** - * Implements a FileProvider for UI purposes. Note that it might not be very - * reliable as long as we have not fixed login and multi repository issues that - * will be addressed in the next version. - * - * NOTE: id used here is the real id of the JCR Node, not the JCR Path - * - * Relies on common approach for JCR file handling implementation. - * - */ -@SuppressWarnings("deprecation") -public class JcrFileProvider implements FileProvider { - - // private Object[] rootNodes; - private Node refNode; - - /** - * Must be set in order for the provider to be able to get current session - * and thus have the ability to get the file node corresponding to a given - * file ID - * - * @param refNode - */ - public void setReferenceNode(Node refNode) { - // FIXME : this introduces some concurrency ISSUES. - this.refNode = refNode; - } - - public byte[] getByteArrayFileFromId(String fileId) { - InputStream fis = null; - byte[] ba = null; - Node child = getFileNodeFromId(fileId); - try { - fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream(); - ba = IOUtils.toByteArray(fis); - - } catch (Exception e) { - throw new EclipseUiException("Stream error while opening file", e); - } finally { - IOUtils.closeQuietly(fis); - } - return ba; - } - - public InputStream getInputStreamFromFileId(String fileId) { - try { - InputStream fis = null; - - Node child = getFileNodeFromId(fileId); - fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream(); - return fis; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot get stream from file node for Id " + fileId, re); - } - } - - /** - * Throws an exception if the node is not found in the current repository (a - * bit like a FileNotFoundException) - * - * @param fileId - * @return Returns the child node of the nt:file node. It is the child node - * that have the jcr:data property where actual file is stored. - * never null - */ - private Node getFileNodeFromId(String fileId) { - try { - Node result = refNode.getSession().getNodeByIdentifier(fileId); - - // rootNodes: for (int j = 0; j < rootNodes.length; j++) { - // // in case we have a classic JCR Node - // if (rootNodes[j] instanceof Node) { - // Node curNode = (Node) rootNodes[j]; - // if (result != null) - // break rootNodes; - // } // Case of a repository Node - // else if (rootNodes[j] instanceof RepositoryNode) { - // Object[] nodes = ((RepositoryNode) rootNodes[j]) - // .getChildren(); - // for (int i = 0; i < nodes.length; i++) { - // Node node = (Node) nodes[i]; - // result = node.getSession().getNodeByIdentifier(fileId); - // if (result != null) - // break rootNodes; - // } - // } - // } - - // Sanity checks - if (result == null) - throw new EclipseUiException("File node not found for ID" + fileId); - - Node child = null; - - boolean isValid = true; - if (!result.isNodeType(NodeType.NT_FILE)) - // useless: mandatory child node - // || !result.hasNode(Property.JCR_CONTENT)) - isValid = false; - else { - child = result.getNode(Property.JCR_CONTENT); - if (!(child.isNodeType(NodeType.NT_RESOURCE) || child.hasProperty(Property.JCR_DATA))) - isValid = false; - } - - if (!isValid) - throw new EclipseUiException("ERROR: In the current implemented model, '" + NodeType.NT_FILE - + "' file node must have a child node named jcr:content " - + "that has a BINARY Property named jcr:data " + "where the actual data is stored"); - return child; - - } catch (RepositoryException re) { - throw new EclipseUiException("Erreur while getting file node of ID " + fileId, re); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java deleted file mode 100644 index fb123992f..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java +++ /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 { - public int compare(Item o1, Item o2) { - try { - // TODO: put folder before files - return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot compare " + o1 + " and " + o2, e); - } - } - -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java deleted file mode 100644 index 54b795f3b..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.eclipse.ui.jcr.util; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.IElementComparer; - -/** Compare JCR nodes based on their JCR identifiers, for use in JFace viewers. */ -public class NodeViewerComparer implements IElementComparer { - - // force comparison on Node IDs only. - public boolean equals(Object elementA, Object elementB) { - if (!(elementA instanceof Node) || !(elementB instanceof Node)) { - return elementA == null ? elementB == null : elementA - .equals(elementB); - } else { - - boolean result = false; - try { - String idA = ((Node) elementA).getIdentifier(); - String idB = ((Node) elementB).getIdentifier(); - result = idA == null ? idB == null : idA.equals(idB); - } catch (RepositoryException re) { - throw new EclipseUiException("cannot compare nodes", re); - } - - return result; - } - } - - public int hashCode(Object element) { - // TODO enhanced this method. - return element.getClass().toString().hashCode(); - } -} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java deleted file mode 100644 index 291d579ac..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java +++ /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 - * JcrFileProvider , it relies on a single session and manages - * nodes with path only. - * - * Note that considered id is the JCR path - * - * Relies on common approach for JCR file handling implementation. - */ -@SuppressWarnings("deprecation") -public class SingleSessionFileProvider implements FileProvider { - - private Session session; - - public SingleSessionFileProvider(Session session) { - this.session = session; - } - - public byte[] getByteArrayFileFromId(String fileId) { - InputStream fis = null; - byte[] ba = null; - Node child = getFileNodeFromId(fileId); - try { - fis = (InputStream) child.getProperty(Property.JCR_DATA) - .getBinary().getStream(); - ba = IOUtils.toByteArray(fis); - - } catch (Exception e) { - throw new EclipseUiException("Stream error while opening file", e); - } finally { - IOUtils.closeQuietly(fis); - } - return ba; - } - - public InputStream getInputStreamFromFileId(String fileId) { - try { - InputStream fis = null; - - Node child = getFileNodeFromId(fileId); - fis = (InputStream) child.getProperty(Property.JCR_DATA) - .getBinary().getStream(); - return fis; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot get stream from file node for Id " - + fileId, re); - } - } - - /** - * - * @param fileId - * @return Returns the child node of the nt:file node. It is the child node - * that have the jcr:data property where actual file is stored. - * never null - */ - private Node getFileNodeFromId(String fileId) { - try { - Node result = null; - result = session.getNode(fileId); - - // Sanity checks - if (result == null) - throw new EclipseUiException("File node not found for ID" + fileId); - - // Ensure that the node have the correct type. - if (!result.isNodeType(NodeType.NT_FILE)) - throw new EclipseUiException( - "Cannot open file children Node that are not of " - + NodeType.NT_RESOURCE + " type."); - - Node child = result.getNodes().nextNode(); - if (child == null || !child.isNodeType(NodeType.NT_RESOURCE)) - throw new EclipseUiException( - "ERROR: IN the current implemented model, " - + NodeType.NT_FILE - + " file node must have one and only one child of the nt:ressource, where actual data is stored"); - return child; - } catch (RepositoryException re) { - throw new EclipseUiException("Erreur while getting file node of ID " - + fileId, re); - } - } -} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java deleted file mode 100644 index 016348cda..000000000 --- a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR helpers. */ -package org.argeo.eclipse.ui.jcr.util; \ No newline at end of file diff --git a/jni/.cproject b/jni/.cproject deleted file mode 100644 index 259cf27db..000000000 --- a/jni/.cproject +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - make - - ide - true - true - true - - - - \ No newline at end of file diff --git a/jni/.project b/jni/.project deleted file mode 100644 index 492a807be..000000000 --- a/jni/.project +++ /dev/null @@ -1,26 +0,0 @@ - - - jni-commons - - - - - - org.eclipse.cdt.managedbuilder.core.genmakebuilder - - - - - org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder - full,incremental, - - - - - - org.eclipse.cdt.core.cnature - org.eclipse.cdt.core.ccnature - org.eclipse.cdt.managedbuilder.core.managedBuildNature - org.eclipse.cdt.managedbuilder.core.ScannerConfigNature - - diff --git a/jni/.settings/language.settings.xml b/jni/.settings/language.settings.xml deleted file mode 100644 index e30ee1672..000000000 --- a/jni/.settings/language.settings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/jni/.settings/org.eclipse.cdt.core.prefs b/jni/.settings/org.eclipse.cdt.core.prefs deleted file mode 100644 index c8ec5df2d..000000000 --- a/jni/.settings/org.eclipse.cdt.core.prefs +++ /dev/null @@ -1,6 +0,0 @@ -doxygen/doxygen_new_line_after_brief=true -doxygen/doxygen_use_brief_tag=false -doxygen/doxygen_use_javadoc_tags=true -doxygen/doxygen_use_pre_tag=false -doxygen/doxygen_use_structural_commands=false -eclipse.preferences.version=1 diff --git a/jni/Makefile b/jni/Makefile deleted file mode 100644 index de2b84c19..000000000 --- a/jni/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -include ../sdk.mk - -JNIDIRS = org_argeo_api_uuid_libuuid - -.PHONY: clean all - -all: - $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir);) - -clean: - rm -rf $(BUILD_DIR) $(SDK_BUILD_BASE)/jni - - - diff --git a/jni/jni.mk b/jni/jni.mk deleted file mode 100644 index 40dde4469..000000000 --- a/jni/jni.mk +++ /dev/null @@ -1,58 +0,0 @@ -TARGET_EXEC := libJava_$(NATIVE_PACKAGE).so - -LDFLAGS = -shared -fPIC -Wl,-soname,$(TARGET_EXEC).$(MAJOR).$(MINOR) $(ADDITIONAL_LIBS) -CFLAGS = -O3 -fPIC - -SRC_DIRS := . - -# -# Generic Argeo -# -BUILD_DIR := $(SDK_BUILD_BASE)/jni/$(NATIVE_PACKAGE) - -# Every folder in ./src will need to be passed to GCC so that it can find header files -INC_DIRS := $(shell find $(SRC_DIRS) -type d) $(JAVA_HOME)/include $(JAVA_HOME)/include/linux $(ADDITIONAL_INCLUDES) - - -.PHONY: clean all ide -all: $(SDK_BUILD_BASE)/jni/$(TARGET_EXEC) - -# Find all the C and C++ files we want to compile -# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise. -SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s') - -# String substitution for every C/C++ file. -# As an example, hello.cpp turns into ./build/hello.cpp.o -OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) - -# String substitution (suffix version without %). -# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d -DEPS := $(OBJS:.o=.d) - -# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag -INC_FLAGS := $(addprefix -I,$(INC_DIRS)) - -# The -MMD and -MP flags together generate Makefiles for us! -# These files will have .d instead of .o as the output. -CPPFLAGS := $(INC_FLAGS) -MMD -MP - -# The final build step. -$(SDK_BUILD_BASE)/jni/$(TARGET_EXEC): $(OBJS) - $(CC) $(OBJS) -o $@ $(LDFLAGS) - -# Build step for C source -$(BUILD_DIR)/%.c.o: %.c - mkdir -p $(dir $@) - $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ - -# Build step for C++ source -$(BUILD_DIR)/%.cpp.o: %.cpp - mkdir -p $(dir $@) - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ - -# Include the .d makefiles. The - at the front suppresses the errors of missing -# Makefiles. Initially, all the .d files will be missing, and we don't want those -# errors to show up. --include $(DEPS) - -# MAKEFILE_DIR := $(dir $(firstword $(MAKEFILE_LIST))) diff --git a/jni/org_argeo_api_uuid_libuuid/.gitignore b/jni/org_argeo_api_uuid_libuuid/.gitignore deleted file mode 100644 index 84c048a73..000000000 --- a/jni/org_argeo_api_uuid_libuuid/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/jni/org_argeo_api_uuid_libuuid/Makefile b/jni/org_argeo_api_uuid_libuuid/Makefile deleted file mode 100644 index cfeb1db55..000000000 --- a/jni/org_argeo_api_uuid_libuuid/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -NATIVE_PACKAGE := org_argeo_api_uuid_libuuid - -ADDITIONAL_INCLUDES = /usr/include/uuid -ADDITIONAL_LIBS = -luuid - -include ../../sdk.mk -include ../jni.mk - diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c deleted file mode 100644 index a5aeed009..000000000 --- a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include -#include "org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h" - -JNIEXPORT void JNICALL Java_org_argeo_api_uuid_libuuid_DirectLibuuidFactory_timeUUID( - JNIEnv *env, jobject uuidFactory, jobject uuidBuf) { - uuid_generate_time((*env)->GetDirectBufferAddress(env, uuidBuf)); -} diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h deleted file mode 100644 index 5f18bf7f1..000000000 --- a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_argeo_api_uuid_libuuid_DirectLibuuidFactory */ - -#ifndef _Included_org_argeo_api_uuid_libuuid_DirectLibuuidFactory -#define _Included_org_argeo_api_uuid_libuuid_DirectLibuuidFactory -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: org_argeo_api_uuid_libuuid_DirectLibuuidFactory - * Method: timeUUID - * Signature: (Ljava/nio/ByteBuffer;)V - */ -JNIEXPORT void JNICALL Java_org_argeo_api_uuid_libuuid_DirectLibuuidFactory_timeUUID - (JNIEnv *, jobject, jobject); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c deleted file mode 100644 index f97b1f452..000000000 --- a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#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, "", - "(JJ)V"); - - jobject jUUID = (*env)->AllocObject(env, uuidClass); - (*env)->CallVoidMethod(env, jUUID, uuidConstructor, msb, lsb); - - return jUUID; -} - -static inline void toBytes(JNIEnv *env, jobject jUUID, uuid_t result) { - - jclass uuidClass = (*env)->FindClass(env, "java/util/UUID"); - jmethodID getMostSignificantBits = (*env)->GetMethodID(env, uuidClass, - "getMostSignificantBits", "()J"); - jmethodID getLeastSignificantBits = (*env)->GetMethodID(env, uuidClass, - "getLeastSignificantBits", "()J"); - - jlong msb = (*env)->CallLongMethod(env, jUUID, getMostSignificantBits); - jlong lsb = (*env)->CallLongMethod(env, jUUID, getLeastSignificantBits); - - for (int i = 0; i < 8; i++) - result[i] = (unsigned char) ((msb >> ((7 - i) * 8)) & 0xff); - for (int i = 8; i < 16; i++) - result[i] = (unsigned char) ((lsb >> ((15 - i) * 8)) & 0xff); -} - -/* - * JNI IMPLEMENTATION - */ - -JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_timeUUID( - JNIEnv *env, jobject uuidFactory) { - uuid_t out; - - uuid_generate_time(out); - return fromBytes(env, out); -} - -JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv5( - JNIEnv *env, jobject uuidFactory, jobject namespaceUuid, - jbyteArray name) { - uuid_t ns; - uuid_t out; - - toBytes(env, namespaceUuid, ns); - jsize length = (*env)->GetArrayLength(env, name); - jbyte *bytes = (*env)->GetByteArrayElements(env, name, 0); - - uuid_generate_sha1(out, ns, bytes, length); - return fromBytes(env, out); -} - -JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv3( - JNIEnv *env, jobject uuidFactory, jobject namespaceUuid, - jbyteArray name) { - uuid_t ns; - uuid_t out; - - toBytes(env, namespaceUuid, ns); - jsize length = (*env)->GetArrayLength(env, name); - jbyte *bytes = (*env)->GetByteArrayElements(env, name, 0); - - uuid_generate_md5(out, ns, bytes, length); - return fromBytes(env, out); -} - -JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_randomUUIDStrong( - JNIEnv *env, jobject uuidFactory) { - uuid_t out; - - uuid_generate_random(out); - return fromBytes(env, out); -} diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h deleted file mode 100644 index ad0ac5e76..000000000 --- a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h +++ /dev/null @@ -1,45 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* 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 index e801ebfb4..81fe078c2 100644 --- a/org.argeo.api.acr/.classpath +++ b/org.argeo.api.acr/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.api.acr/bnd.bnd b/org.argeo.api.acr/bnd.bnd index 6e839c070..ea0a76ba9 100644 --- a/org.argeo.api.acr/bnd.bnd +++ b/org.argeo.api.acr/bnd.bnd @@ -1,4 +1,5 @@ Import-Package: \ -javax.security.* +javax.security.*, \ +javax.xml.* Export-Package: org.argeo.api.acr.* \ No newline at end of file diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ArgeoNamespace.java b/org.argeo.api.acr/src/org/argeo/api/acr/ArgeoNamespace.java new file mode 100644 index 000000000..98131d13e --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ArgeoNamespace.java @@ -0,0 +1,14 @@ +package org.argeo.api.acr; + +/** Namespaces declared by Argeo. */ +public enum ArgeoNamespace { + ; + + public final static String CR_NAMESPACE_URI = "http://www.argeo.org/ns/cr"; + public final static String CR_DEFAULT_PREFIX = "cr"; + public final static String LDAP_NAMESPACE_URI = "http://www.argeo.org/ns/ldap"; + public final static String LDAP_DEFAULT_PREFIX = "ldap"; + public final static String ROLE_NAMESPACE_URI = "http://www.argeo.org/ns/role"; + public final static String ROLE_DEFAULT_PREFIX = "role"; + +} 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 index 41600331a..c7023f1a3 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java @@ -4,9 +4,8 @@ package org.argeo.api.acr; * An attribute type MUST consistently parse a string to an object so that * parse(obj.toString()).equals(obj) is verified. * {@link #format(Object)} can be overridden to provide more efficient - * implementations but the returned - * String MUST be the same, that is format(obj).equals(obj.toString()) - * is verified. + * implementations but the returned String MUST be the same, that + * is format(obj).equals(obj.toString()) is verified. */ public interface AttributeFormatter { /** Parses a String to a Java object. */ diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java b/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java deleted file mode 100644 index b83fe301e..000000000 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.argeo.api.acr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.StringTokenizer; - -/** A name that can be expressed with various conventions. */ -public class CompositeString { - public final static Character UNDERSCORE = Character.valueOf('_'); - public final static Character SPACE = Character.valueOf(' '); - public final static Character DASH = Character.valueOf('-'); - - private final String[] parts; - - // optimisation - private final int hashCode; - - public CompositeString(String str) { - Objects.requireNonNull(str, "String cannot be null"); - if ("".equals(str.trim())) - throw new IllegalArgumentException("String cannot be empty"); - if (!str.equals(str.trim())) - throw new IllegalArgumentException("String must be trimmed"); - this.parts = toParts(str); - hashCode = hashCode(this.parts); - } - - public String toString(char separator, boolean upperCase) { - StringBuilder sb = null; - for (String part : parts) { - if (sb == null) { - sb = new StringBuilder(); - } else { - sb.append(separator); - } - sb.append(upperCase ? part.toUpperCase() : part); - } - return sb.toString(); - } - - public String toStringCaml(boolean firstCharUpperCase) { - StringBuilder sb = null; - for (String part : parts) { - if (sb == null) {// first - sb = new StringBuilder(); - sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0)); - } else { - sb.append(Character.toUpperCase(part.charAt(0))); - } - - if (part.length() > 1) - sb.append(part.substring(1)); - } - return sb.toString(); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof CompositeString)) - return false; - - CompositeString other = (CompositeString) obj; - return Arrays.equals(parts, other.parts); - } - - @Override - public String toString() { - return toString(DASH, false); - } - - public static String[] toParts(String str) { - Character separator = null; - if (str.indexOf(UNDERSCORE) >= 0) { - checkNo(str, SPACE); - checkNo(str, DASH); - separator = UNDERSCORE; - } else if (str.indexOf(DASH) >= 0) { - checkNo(str, SPACE); - checkNo(str, UNDERSCORE); - separator = DASH; - } else if (str.indexOf(SPACE) >= 0) { - checkNo(str, DASH); - checkNo(str, UNDERSCORE); - separator = SPACE; - } - - List 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 index edcfaea10..f52ab31b8 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java @@ -1,10 +1,15 @@ package org.argeo.api.acr; +import static org.argeo.api.acr.NamespaceUtils.unqualified; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; -import javax.xml.XMLConstants; import javax.xml.namespace.QName; /** @@ -24,30 +29,50 @@ public interface Content extends Iterable, Map { Optional get(QName key, Class clss); - default Object get(String key) { - if (key.indexOf(':') >= 0) - throw new IllegalArgumentException("Name " + key + " has a prefix"); - return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX)); + Class getType(QName key); + + boolean isMultiple(QName key); + + List getMultiple(QName key, Class clss); + + /* + * ATTRIBUTES OPERATION HELPERS + */ + default boolean containsKey(QNamed key) { + return containsKey(key.qName()); } - default Object put(String key, Object value) { - if (key.indexOf(':') >= 0) - throw new IllegalArgumentException("Name " + key + " has a prefix"); - return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value); + default Optional get(QNamed key, Class clss) { + return get(key.qName(), clss); } - default Object remove(String key) { - if (key.indexOf(':') >= 0) - throw new IllegalArgumentException("Name " + key + " has a prefix"); - return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX)); + default Object get(QNamed key) { + return get(key.qName()); } - Class getType(QName key); + default Object put(QNamed key, Object value) { + return put(key.qName(), value); + } - boolean isMultiple(QName key); + default Object remove(QNamed key) { + return remove(key.qName()); + } + + // TODO do we really need the helpers below? + + default Object get(String key) { + return get(unqualified(key)); + } + + default Object put(String key, Object value) { + return put(unqualified(key), value); + } - Optional> getMultiple(QName key, Class clss); + default Object remove(String key) { + return remove(unqualified(key)); + } + @SuppressWarnings("unchecked") default List getMultiple(QName key) { Class type; try { @@ -55,46 +80,183 @@ public interface Content extends Iterable, Map { } catch (ClassCastException e) { throw new IllegalArgumentException("Requested type is not the default type"); } - Optional> res = getMultiple(key, type); - if (res == null) - return null; - else { - if (res.isEmpty()) - throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type); - return res.get(); - } + List res = getMultiple(key, type); + return res; +// if (res == null) +// return null; +// else { +// if (res.isEmpty()) +// throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type); +// return res.get(); +// } } /* * CONTENT OPERATIONS */ +// default CompletionStage edit(Consumer work) { +// return CompletableFuture.supplyAsync(() -> { +// work.accept(this); +// return this; +// }).minimalCompletionStage(); +// } + Content add(QName name, QName... classes); default Content add(String name, QName... classes) { - if (name.indexOf(':') >= 0) - throw new IllegalArgumentException("Name " + name + " has a prefix"); - return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes); + return add(unqualified(name), classes); } void remove(); + /* + * TYPING + */ + List getContentClasses(); + + default void addContentClasses(QName... contentClass) { + throw new UnsupportedOperationException("Adding content classes to " + getPath() + " is not supported"); + } + + /** AND */ + default boolean isContentClass(QName... contentClass) { + List contentClasses = getContentClasses(); + for (QName cClass : contentClass) { + if (!contentClasses.contains(cClass)) + return false; + } + return true; + } + + /** AND */ + default boolean isContentClass(QNamed... contentClass) { + List lst = new ArrayList<>(); + for (QNamed qNamed : contentClass) + lst.add(qNamed.qName()); + return isContentClass(lst.toArray(new QName[lst.size()])); + } + + /** OR */ + default boolean hasContentClass(QName... contentClass) { + List contentClasses = getContentClasses(); + for (QName cClass : contentClass) { + if (contentClasses.contains(cClass)) + return true; + } + return false; + } + + /** OR */ + default boolean hasContentClass(QNamed... contentClass) { + List lst = new ArrayList<>(); + for (QNamed qNamed : contentClass) + lst.add(qNamed.qName()); + return hasContentClass(lst.toArray(new QName[lst.size()])); + } + + /* + * SIBLINGS + */ + + default int getSiblingIndex() { + return 1; + } + /* * DEFAULT METHODS */ - default A adapt(Class clss) throws IllegalArgumentException { - throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName()); + default A adapt(Class clss) { + throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName()); + } + + default C open(Class clss) throws IOException { + throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName()); } - default C open(Class clss) throws Exception, IllegalArgumentException { - throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName()); + default CompletableFuture write(Class clss) { + throw new UnsupportedOperationException("Cannot write content " + this + " as " + clss.getName()); } /* - * CONVENIENCE METHODS + * CHILDREN */ -// default String attr(String key) { -// return get(key, String.class); -// } + + default boolean hasChild(QName name) { + for (Content child : this) { + if (child.getName().equals(name)) + return true; + } + return false; + } + + default boolean hasChild(QNamed name) { + return hasChild(name.qName()); + } + + default Content anyOrAddChild(QName name, QName... classes) { + Content child = anyChild(name); + if (child != null) + return child; + return this.add(name, classes); + } + + default Content anyOrAddChild(String name, QName... classes) { + return anyOrAddChild(unqualified(name), classes); + } + + /** Any child with this name, or null if there is none */ + default Content anyChild(QName name) { + for (Content child : this) { + if (child.getName().equals(name)) + return child; + } + return null; + } + + default List children(QName name) { + List res = new ArrayList<>(); + for (Content child : this) { + if (child.getName().equals(name)) + res.add(child); + } + return res; + } + + default Optional soleChild(QName name) { + List res = children(name); + if (res.isEmpty()) + return Optional.empty(); + if (res.size() > 1) + throw new IllegalStateException(this + " has multiple children with name " + name); + return Optional.of(res.get(0)); + } + + default Content child(QName name) { + return soleChild(name).orElseThrow(); + } + + default Content child(QNamed name) { + return child(name.qName()); + } + + /* + * ATTR AS STRING + */ + default String attr(QName key) { + // TODO check String type? + Object obj = get(key); + if (obj == null) + return null; + return obj.toString(); + } + + default String attr(QNamed key) { + return attr(key.qName()); + } + + default String attr(String key) { + return attr(unqualified(key)); + } // // default String attr(Object key) { // return key != null ? attr(key.toString()) : attr(null); 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 index 341a3e297..113f98da0 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java @@ -24,7 +24,7 @@ public class ContentName extends QName { * The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS * namespace, to be used as a base for the namespaces. * - * @see https://www.w3.org/TR/xml-names/#ns-decl + * @see "https://www.w3.org/TR/xml-names/#ns-decl" */ // uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/ // NOTE : must be declared before default namespaces @@ -41,6 +41,10 @@ public class ContentName extends QName { // private final UUID uuid; + public ContentName(String namespaceURI, String localPart) { + super(namespaceURI, localPart, checkPrefix(RuntimeNamespaceContext.getNamespaceContext(), namespaceURI)); + } + public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) { super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI)); } @@ -48,7 +52,7 @@ public class ContentName extends QName { private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) { Objects.requireNonNull(nsContext, "Namespace context cannot be null"); Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null"); - String prefix = nsContext.getNamespaceURI(namespaceURI); + String prefix = nsContext.getPrefix(namespaceURI); if (prefix == null) throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext); return prefix; @@ -80,7 +84,7 @@ public class ContentName extends QName { @Override public String toString() { - return toPrefixedString(); + return toQNameString(); } @Override diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java deleted file mode 100644 index e3c721fef..000000000 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.argeo.api.acr; - -import java.util.Collections; -import java.util.Iterator; -import java.util.function.Supplier; - -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; - -public interface ContentNameSupplier extends Supplier, 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 getPrefixes(String namespaceURI) { - Iterator 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 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 index b86c92c1e..51efb3860 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java @@ -1,16 +1,35 @@ package org.argeo.api.acr; -/** When a countent was requested which does not exists, equivalent to HTTP code 404.*/ +/** + * When a content was requested which does not exists, equivalent to HTTP code + * 404. + */ public class ContentNotFoundException extends RuntimeException { private static final long serialVersionUID = -8629074900713760886L; - public ContentNotFoundException(String message, Throwable cause) { - super(message, cause); + private final String path; + + public ContentNotFoundException(ContentSession session, String path, Throwable cause) { + super(message(session, path), cause); + this.path = path; + // we don't keep reference to the session for security reasons + } + + public ContentNotFoundException(ContentSession session, String path) { + this(session, path, (String) null); } - public ContentNotFoundException(String message) { - super(message); + public ContentNotFoundException(ContentSession session, String path, String message) { + super(message != null ? message : message(session, path)); + this.path = path; + // we don't keep reference to the session for security reasons } - + private static String message(ContentSession session, String path) { + return "Content " + path + "cannot be found."; + } + + public String getPath() { + return path; + } } 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 index 215bb9e22..b7d37dc10 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java @@ -1,12 +1,11 @@ package org.argeo.api.acr; import java.util.Locale; -import java.util.Objects; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; import javax.security.auth.Subject; -import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; -import javax.xml.namespace.QName; public interface ContentSession extends NamespaceContext { Subject getSubject(); @@ -14,36 +13,8 @@ public interface ContentSession extends NamespaceContext { Locale getLocale(); Content get(String path); + + boolean exists(String path); - /* - * NAMESPACE CONTEXT - */ - - default ContentName parsePrefixedName(String nameWithPrefix) { - Objects.requireNonNull(nameWithPrefix, "Name cannot be null"); - if (nameWithPrefix.charAt(0) == '{') { - return new ContentName(QName.valueOf(nameWithPrefix), this); - } - int index = nameWithPrefix.indexOf(':'); - if (index < 0) { - return new ContentName(nameWithPrefix); - } - String prefix = nameWithPrefix.substring(0, index); - // TODO deal with empty name? - String localName = nameWithPrefix.substring(index + 1); - String namespaceURI = getNamespaceURI(prefix); - if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) - throw new IllegalStateException("Prefix " + prefix + " is unbound."); - return new ContentName(namespaceURI, localName, prefix); - } - - default String toPrefixedName(QName name) { - if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI())) - return name.getLocalPart(); - String prefix = getPrefix(name.getNamespaceURI()); - if (prefix == null) - throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound."); - return prefix + ":" + name.getLocalPart(); - } - + CompletionStage edit(Consumer work); } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java deleted file mode 100644 index 2036c8622..000000000 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.argeo.api.acr; - -import java.io.PrintStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; -import java.util.function.BiConsumer; - -import javax.xml.namespace.QName; - -public class ContentUtils { - public static void traverse(Content content, BiConsumer doIt) { - traverse(content, doIt, 0); - } - - public static void traverse(Content content, BiConsumer 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(""); - } - } - } - - 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 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 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 index ffa28af0a..3e12fb1c8 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java @@ -1,37 +1,52 @@ package org.argeo.api.acr; +import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; + import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; -import javax.xml.XMLConstants; +import javax.xml.namespace.QName; /** * Minimal standard attribute types that MUST be supported. All related classes * belong to java.base and can be implicitly derived form a given - * String. + * String. */ -public enum CrAttributeType implements ContentNameSupplier { - BOOLEAN(Boolean.class, new BooleanFormatter()), // - INTEGER(Integer.class, new IntegerFormatter()), // - LONG(Long.class, new LongFormatter()), // - DOUBLE(Double.class, new DoubleFormatter()), // +public enum CrAttributeType { + BOOLEAN(Boolean.class, W3C_XML_SCHEMA_NS_URI, "boolean", new BooleanFormatter()), // + INTEGER(Integer.class, W3C_XML_SCHEMA_NS_URI, "integer", new IntegerFormatter()), // + LONG(Long.class, W3C_XML_SCHEMA_NS_URI, "long", new LongFormatter()), // + DOUBLE(Double.class, W3C_XML_SCHEMA_NS_URI, "double", new DoubleFormatter()), // // we do not support short and float, like recent additions to Java // (e.g. optional primitives) - DATE_TIME(Instant.class, new InstantFormatter()), // - UUID(UUID.class, new UuidFormatter()), // - ANY_URI(URI.class, new UriFormatter()), // - STRING(String.class, new StringFormatter()), // + DATE_TIME(Instant.class, W3C_XML_SCHEMA_NS_URI, "dateTime", new InstantFormatter()), // + UUID(UUID.class, ArgeoNamespace.CR_NAMESPACE_URI, "uuid", new UuidFormatter()), // + ANY_URI(URI.class, W3C_XML_SCHEMA_NS_URI, "anyUri", new UriFormatter()), // + STRING(String.class, W3C_XML_SCHEMA_NS_URI, "string", new StringFormatter()), // ; private final Class clss; private final AttributeFormatter formatter; - private CrAttributeType(Class clss, AttributeFormatter formatter) { + private final ContentName qName; + + private CrAttributeType(Class clss, String namespaceUri, String localName, AttributeFormatter formatter) { this.clss = clss; this.formatter = formatter; + + qName = new ContentName(namespaceUri, localName, RuntimeNamespaceContext.getNamespaceContext()); + } + + public QName getqName() { + return qName; } public Class getClss() { @@ -42,22 +57,23 @@ public enum CrAttributeType implements ContentNameSupplier { return formatter; } - @Override - public String getDefaultPrefix() { - if (equals(UUID)) - return CrName.CR_DEFAULT_PREFIX; - else - return "xs"; - } - - @Override - public String getNamespaceURI() { - if (equals(UUID)) - return CrName.CR_NAMESPACE_URI; - else - return XMLConstants.W3C_XML_SCHEMA_NS_URI; - } +// @Override +// public String getDefaultPrefix() { +// if (equals(UUID)) +// return CrName.CR_DEFAULT_PREFIX; +// else +// return "xs"; +// } +// +// @Override +// public String getNamespaceURI() { +// if (equals(UUID)) +// return CrName.CR_NAMESPACE_URI; +// else +// return XMLConstants.W3C_XML_SCHEMA_NS_URI; +// } + /** Default parsing procedure from a String to an object. */ public static Object parse(String str) { if (str == null) throw new IllegalArgumentException("String cannot be null"); @@ -109,10 +125,76 @@ public enum CrAttributeType implements ContentNameSupplier { // silent } + // TODO support QName as a type? It would require a NamespaceContext + // see https://www.oreilly.com/library/view/xml-schema/0596002521/re91.html + // default return STRING.getFormatter().parse(str); } + /** + * Cast well know java types based on {@link Object#toString()} of the provided + * object. + * + */ + @SuppressWarnings("unchecked") + public static Optional cast(Class clss, Object value) { + // TODO Or should we? + Objects.requireNonNull(value, "Cannot cast a null value"); + if (String.class.isAssignableFrom(clss)) { + return Optional.of((T) value.toString()); + } + // Numbers + else if (Long.class.isAssignableFrom(clss)) { + if (value instanceof Long) + return Optional.of((T) value); + return Optional.of((T) Long.valueOf(value.toString())); + } else if (Integer.class.isAssignableFrom(clss)) { + if (value instanceof Integer) + return Optional.of((T) value); + return Optional.of((T) Integer.valueOf(value.toString())); + } else if (Double.class.isAssignableFrom(clss)) { + if (value instanceof Double) + return Optional.of((T) value); + return Optional.of((T) Double.valueOf(value.toString())); + } + // Numbers +// else if (Number.class.isAssignableFrom(clss)) { +// if (value instanceof Number) +// return Optional.of((T) value); +// return Optional.of((T) Number.valueOf(value.toString())); +// } + return Optional.empty(); + } + + /** Utility to convert a data: URI to bytes. */ + public static byte[] bytesFromDataURI(URI uri) { + if (!"data".equals(uri.getScheme())) + throw new IllegalArgumentException("URI must have 'data' as a scheme"); + String schemeSpecificPart = uri.getSchemeSpecificPart(); + int commaIndex = schemeSpecificPart.indexOf(','); + String prefix = schemeSpecificPart.substring(0, commaIndex); + List info = Arrays.asList(prefix.split(";")); + if (!info.contains("base64")) + throw new IllegalArgumentException("URI must specify base64"); + + String base64Str = schemeSpecificPart.substring(commaIndex + 1); + return Base64.getDecoder().decode(base64Str); + + } + + /** Utility to convert bytes to a data: URI. */ + public static URI bytesToDataURI(byte[] arr) { + String base64Str = Base64.getEncoder().encodeToString(arr); + try { + final String PREFIX = "data:application/octet-stream;base64,"; + return new URI(PREFIX + base64Str); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e); + } + + } + static class BooleanFormatter implements AttributeFormatter { /** 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 index 099be9fda..ead47377b 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java @@ -1,58 +1,59 @@ package org.argeo.api.acr; /** Standard names. */ -public enum CrName implements ContentNameSupplier { +public enum CrName implements QNamed { /* * TYPES */ - COLLECTION, // a collection type +// collection, // a collection type /* * ATTRIBUTES */ - UUID, // the UUID of a content + uuid, // the UUID of a content + mount, // a mount point +// cc, // content class /* * ATTRIBUTES FROM FILE SEMANTICS */ - CREATION_TIME, // - LAST_MODIFIED_TIME, // - SIZE, // - FILE_KEY, // - OWNER, // - GROUP, // - PERMISSIONS, // +// creationTime, // +// lastModifiedTime, // +// size, // + fileKey, // +// owner, // +// group, // + permissions, // /* * CONTENT NAMES */ - ROOT, + root, // ; - public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr"; - public final static String CR_DEFAULT_PREFIX = "cr"; - private final ContentName value; + - CrName() { - value = toContentName(); - } +// private final ContentName value; - @Override - public ContentName get() { - return value; - } +// CrName() { +// value = new ContentName(CR_NAMESPACE_URI, name(), RuntimeNamespaceContext.getNamespaceContext()); +// } +// +// public QName qName() { +// return value; +// } @Override - public String getNamespaceURI() { - return CR_NAMESPACE_URI; + public String getNamespace() { + return ArgeoNamespace.CR_NAMESPACE_URI; } @Override public String getDefaultPrefix() { - return CR_DEFAULT_PREFIX; + return ArgeoNamespace.CR_DEFAULT_PREFIX; } } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/DName.java b/org.argeo.api.acr/src/org/argeo/api/acr/DName.java new file mode 100644 index 000000000..be065a8d9 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/DName.java @@ -0,0 +1,45 @@ +package org.argeo.api.acr; + +/** + * Name for core concepts with the same semantics as defined in the WebDav + * standard and extensions. + * + * @see "http://www.webdav.org/specs/rfc4918.html" + * @see "http://www.webdav.org/specs/rfc3744.html" + */ +public enum DName implements QNamed + +{ + // RFC4918 (WebDav) properties used as CR attr + creationdate, // + displayname, // + getcontentlanguage, // + getcontentlength, // + getcontenttype, // + getetag, // + getlastmodified, // + resourcetype, // + + // RFC4918 (WebDav) value used as CR class + collection, // + + // RFC3744 (ACL) properties uase as CR attr + owner, // + group, // + // + ; + + public final static String WEBDAV_NAMESPACE_URI = "DAV:"; + public final static String WEBDAV_DEFAULT_PREFIX = "D"; + + @Override + public String getNamespace() { + return WEBDAV_NAMESPACE_URI; + } + + @Override + public String getDefaultPrefix() { + return WEBDAV_DEFAULT_PREFIX; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java new file mode 100644 index 000000000..df582868b --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java @@ -0,0 +1,145 @@ +package org.argeo.api.acr; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; + +public class NamespaceUtils { + + public static ContentName parsePrefixedName(String nameWithPrefix) { + return parsePrefixedName(RuntimeNamespaceContext.getNamespaceContext(), nameWithPrefix); + } + + public static ContentName parsePrefixedName(NamespaceContext nameSpaceContext, String nameWithPrefix) { + Objects.requireNonNull(nameWithPrefix, "Name cannot be null"); + if (nameWithPrefix.charAt(0) == '{') { + return new ContentName(QName.valueOf(nameWithPrefix), nameSpaceContext); + } + int index = nameWithPrefix.indexOf(':'); + if (index < 0) { + return new ContentName(nameWithPrefix); + } + String prefix = nameWithPrefix.substring(0, index); + // TODO deal with empty name? + String localName = nameWithPrefix.substring(index + 1); + String namespaceURI = nameSpaceContext.getNamespaceURI(prefix); + if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) + throw new IllegalStateException("Prefix " + prefix + " is unbound."); + return new ContentName(namespaceURI, localName, prefix); + } + + public static String toPrefixedName(QName name) { + return toPrefixedName(RuntimeNamespaceContext.getNamespaceContext(), name); + } + + public static String toPrefixedName(NamespaceContext nameSpaceContext, QName name) { + if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI())) + return name.getLocalPart(); + String prefix = nameSpaceContext.getPrefix(name.getNamespaceURI()); + if (prefix == null) + throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound."); + return prefix + ":" + name.getLocalPart(); + } + + public final static Comparator QNAME_COMPARATOR = new Comparator() { + + @Override + public int compare(QName qn1, QName qn2) { + if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace + return qn1.getLocalPart().compareTo(qn2.getLocalPart()); + } else { + return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI()); + } + } + + }; + + public static boolean hasNamespace(QName qName) { + return !qName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI); + } + + public static void checkNoPrefix(String unqualified) { + if (unqualified.indexOf(':') >= 0) + throw new IllegalArgumentException("Name " + unqualified + " has a prefix"); + } + + public static QName unqualified(String name) { + checkNoPrefix(name); + return new ContentName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX); + + } + + /* + * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext + */ + public static String getStandardPrefix(String namespaceURI) { + if (namespaceURI == null) + throw new IllegalArgumentException("Namespace URI cannot be null"); + if (XMLConstants.XML_NS_URI.equals(namespaceURI)) + return XMLConstants.XML_NS_PREFIX; + else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI)) + return XMLConstants.XMLNS_ATTRIBUTE; + return null; + } + + public static Iterator getStandardPrefixes(String namespaceURI) { + String prefix = getStandardPrefix(namespaceURI); + if (prefix == null) + return null; + return Collections.singleton(prefix).iterator(); + } + + public static String getStandardNamespaceURI(String prefix) { + if (prefix == null) + throw new IllegalArgumentException("Prefix cannot be null"); + if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + return XMLConstants.XML_NS_URI; + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + return null; + } + + public static String getNamespaceURI(Function mapping, String prefix) { + String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix); + if (namespaceURI != null) + return namespaceURI; + if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) + return XMLConstants.NULL_NS_URI; + namespaceURI = mapping.apply(prefix); + if (namespaceURI != null) + return namespaceURI; + return XMLConstants.NULL_NS_URI; + } + + public static String getPrefix(Function mapping, String namespaceURI) { + String prefix = NamespaceUtils.getStandardPrefix(namespaceURI); + if (prefix != null) + return prefix; + if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) + return XMLConstants.DEFAULT_NS_PREFIX; + return mapping.apply(namespaceURI); + } + + public static Iterator getPrefixes(Function> mapping, String namespaceURI) { + Iterator standard = NamespaceUtils.getStandardPrefixes(namespaceURI); + if (standard != null) + return standard; + if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) + return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator(); + Set prefixes = mapping.apply(namespaceURI); + assert prefixes != null; + return prefixes.iterator(); + } + + /** singleton */ + private NamespaceUtils() { + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java b/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java new file mode 100644 index 000000000..063a7d321 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java @@ -0,0 +1,47 @@ +package org.argeo.api.acr; + +import java.util.function.Supplier; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; + +/** An optionally qualified name. Primarily meant to be used in enums. */ +public interface QNamed extends Supplier { + String name(); + + /** To be overridden when XML naming is not compatible with Java naming. */ + default String localName() { + return name(); + } + + default QName qName() { + return new ContentName(getNamespace(), localName(), getDefaultPrefix()); + } + + default String get(NamespaceContext namespaceContext) { + return namespaceContext.getPrefix(getNamespace()) + ":" + localName(); + } + + default String get() { + return getDefaultPrefix() + ":" + localName(); + } + + String getNamespace(); + + String getDefaultPrefix(); + + /** To be used by enums without namespace (typically XML attributes). */ + static interface Unqualified extends QNamed { + @Override + default String getNamespace() { + return XMLConstants.NULL_NS_URI; + } + + @Override + default String getDefaultPrefix() { + return XMLConstants.DEFAULT_NS_PREFIX; + } + + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java new file mode 100644 index 000000000..1c55156ee --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java @@ -0,0 +1,95 @@ +package org.argeo.api.acr; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + +/** + * Programmatically defined {@link NamespaceContext}, code contributing + * namespaces MUST register here with a single default prefix. + */ +public class RuntimeNamespaceContext implements NamespaceContext { + public final static String XSD_DEFAULT_PREFIX = "xs"; + public final static String XSD_INSTANCE_DEFAULT_PREFIX = "xsi"; + + private NavigableMap prefixes = new TreeMap<>(); + private NavigableMap namespaces = new TreeMap<>(); + + @Override + public String getPrefix(String namespaceURI) { + return NamespaceUtils.getPrefix((ns) -> { + String prefix = namespaces.get(ns); + if (prefix == null) + throw new IllegalStateException("Namespace " + ns + " is not registered."); + return prefix; + }, namespaceURI); + } + + @Override + public String getNamespaceURI(String prefix) { + return NamespaceUtils.getNamespaceURI((p) -> { + String ns = prefixes.get(p); + if (ns == null) + throw new IllegalStateException("Prefix " + p + " is not registered."); + return ns; + }, prefix); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + return Collections.singleton(getPrefix(namespaceURI)).iterator(); + } + + /* + * STATIC + */ + + private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext(); + + static { + // Standard + register(XMLConstants.XML_NS_URI, XMLConstants.XML_NS_PREFIX); + register(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE); + + // Common + register(XMLConstants.W3C_XML_SCHEMA_NS_URI, XSD_DEFAULT_PREFIX); + register(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XSD_INSTANCE_DEFAULT_PREFIX); + + // Argeo specific + register(ArgeoNamespace.CR_NAMESPACE_URI, ArgeoNamespace.CR_DEFAULT_PREFIX); + register(ArgeoNamespace.LDAP_NAMESPACE_URI, ArgeoNamespace.LDAP_DEFAULT_PREFIX); + register(ArgeoNamespace.ROLE_NAMESPACE_URI, ArgeoNamespace.ROLE_DEFAULT_PREFIX); + } + + public static NamespaceContext getNamespaceContext() { + return INSTANCE; + } + + public static Map getPrefixes() { + return Collections.unmodifiableNavigableMap(INSTANCE.prefixes); + } + + public synchronized static void register(String namespaceURI, String prefix) { + NavigableMap prefixes = INSTANCE.prefixes; + NavigableMap namespaces = INSTANCE.namespaces; + if (prefixes.containsKey(prefix)) { + String ns = prefixes.get(prefix); + if (ns.equals(namespaceURI)) + return; // ignore silently + throw new IllegalStateException("Prefix " + prefix + " is already registered with namespace URI " + ns); + } + if (namespaces.containsKey(namespaceURI)) { + String p = namespaces.get(namespaceURI); + if (p.equals(prefix)) + return; // ignore silently + throw new IllegalStateException("Namespace " + namespaceURI + " is already registered with prefix " + p); + } + prefixes.put(prefix, namespaceURI); + namespaces.put(namespaceURI, prefix); + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/Distinguished.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/Distinguished.java new file mode 100644 index 000000000..52556cf22 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/Distinguished.java @@ -0,0 +1,36 @@ +package org.argeo.api.acr.ldap; + +import java.util.EnumSet; +import java.util.Set; +import java.util.TreeSet; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +/** + * An object that can be identified with an X.500 distinguished name. + * + * @see "https://tools.ietf.org/html/rfc1779" + */ +public interface Distinguished { + /** The related distinguished name. */ + String dn(); + + /** The related distinguished name as an {@link LdapName}. */ + default LdapName distinguishedName() { + try { + return new LdapName(dn()); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e); + } + } + + /** List all DNs of an enumeration as strings. */ + static Set enumToDns(EnumSet enumSet) { + Set res = new TreeSet<>(); + for (Enum enm : enumSet) { + res.add(((Distinguished) enm).dn()); + } + return res; + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAcrUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAcrUtils.java new file mode 100644 index 000000000..7ef64c25a --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAcrUtils.java @@ -0,0 +1,31 @@ +package org.argeo.api.acr.ldap; + +import java.util.Locale; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; + +/** Utilities around ACR and LDAP conventions. */ +public class LdapAcrUtils { + + /** singleton */ + private LdapAcrUtils() { + } + + public static Object getLocalized(Content content, QName key, Locale locale) { + if (locale == null) + throw new IllegalArgumentException("A locale must be specified"); + Object value = null; + if (locale.getCountry() != null && !locale.getCountry().equals("")) + value = content.get(new ContentName(key.getNamespaceURI(), + key.getLocalPart() + ";lang-" + locale.getLanguage() + "-" + locale.getCountry())); + if (value == null) + value = content + .get(new ContentName(key.getNamespaceURI(), key.getLocalPart() + ";lang-" + locale.getLanguage())); + if (value == null) + value = content.get(key); + return value; + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAttr.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAttr.java new file mode 100644 index 000000000..81b36ec53 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAttr.java @@ -0,0 +1,368 @@ +package org.argeo.api.acr.ldap; + +import static org.argeo.api.acr.ArgeoNamespace.LDAP_DEFAULT_PREFIX; +import static org.argeo.api.acr.ArgeoNamespace.LDAP_NAMESPACE_URI; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.QNamed; +import org.argeo.api.acr.RuntimeNamespaceContext; + +/** + * Standard LDAP attributes as per:
+ * -
Standard LDAP
+ * - Kerberos + * LDAP (partial) + */ +public enum LdapAttr implements QNamed, SpecifiedName { + /** */ + uid("0.9.2342.19200300.100.1.1", "RFC 4519"), + /** */ + mail("0.9.2342.19200300.100.1.3", "RFC 4524"), + /** */ + info("0.9.2342.19200300.100.1.4", "RFC 4524"), + /** */ + drink("0.9.2342.19200300.100.1.5", "RFC 4524"), + /** */ + roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"), + /** */ + photo("0.9.2342.19200300.100.1.7", "RFC 2798"), + /** */ + userClass("0.9.2342.19200300.100.1.8", "RFC 4524"), + /** */ + host("0.9.2342.19200300.100.1.9", "RFC 4524"), + /** */ + manager("0.9.2342.19200300.100.1.10", "RFC 4524"), + /** */ + documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"), + /** */ + documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"), + /** */ + documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"), + /** */ + documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"), + /** */ + documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"), + /** */ + homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"), + /** */ + secretary("0.9.2342.19200300.100.1.21", "RFC 4524"), + /** */ + dc("0.9.2342.19200300.100.1.25", "RFC 4519"), + /** */ + associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"), + /** */ + associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"), + /** */ + homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"), + /** */ + personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"), + /** */ + mobile("0.9.2342.19200300.100.1.41", "RFC 4524"), + /** */ + pager("0.9.2342.19200300.100.1.42", "RFC 4524"), + /** */ + co("0.9.2342.19200300.100.1.43", "RFC 4524"), + /** */ + uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"), + /** */ + organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"), + /** */ + buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"), + /** */ + audio("0.9.2342.19200300.100.1.55", "RFC 2798"), + /** */ + documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"), + /** */ + jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"), + /** */ + vendorName("1.3.6.1.1.4", "RFC 3045"), + /** */ + vendorVersion("1.3.6.1.1.5", "RFC 3045"), + /** */ + entryUUID("1.3.6.1.1.16.4", "RFC 4530"), + /** */ + entryDN("1.3.6.1.1.20", "RFC 5020"), + /** */ + labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"), + /** */ + numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"), + /** */ + namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"), + /** */ + altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"), + /** */ + supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"), + /** */ + supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"), + /** */ + supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"), + /** */ + supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"), + /** */ + ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"), + /** */ + supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"), + /** */ + authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"), + /** */ + supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"), + /** */ + inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"), + /** */ + blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"), + /** */ + objectClass("2.5.4.0", "RFC 4512"), + /** */ + aliasedObjectName("2.5.4.1", "RFC 4512"), + /** */ + cn("2.5.4.3", "RFC 4519"), + /** */ + sn("2.5.4.4", "RFC 4519"), + /** */ + serialNumber("2.5.4.5", "RFC 4519"), + /** */ + c("2.5.4.6", "RFC 4519"), + /** */ + l("2.5.4.7", "RFC 4519"), + /** */ + st("2.5.4.8", "RFC 4519"), + /** */ + street("2.5.4.9", "RFC 4519"), + /** */ + o("2.5.4.10", "RFC 4519"), + /** */ + ou("2.5.4.11", "RFC 4519"), + /** */ + title("2.5.4.12", "RFC 4519"), + /** */ + description("2.5.4.13", "RFC 4519"), + /** */ + searchGuide("2.5.4.14", "RFC 4519"), + /** */ + businessCategory("2.5.4.15", "RFC 4519"), + /** */ + postalAddress("2.5.4.16", "RFC 4519"), + /** */ + postalCode("2.5.4.17", "RFC 4519"), + /** */ + postOfficeBox("2.5.4.18", "RFC 4519"), + /** */ + physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"), + /** */ + telephoneNumber("2.5.4.20", "RFC 4519"), + /** */ + telexNumber("2.5.4.21", "RFC 4519"), + /** */ + teletexTerminalIdentifier("2.5.4.22", "RFC 4519"), + /** */ + facsimileTelephoneNumber("2.5.4.23", "RFC 4519"), + /** */ + x121Address("2.5.4.24", "RFC 4519"), + /** */ + internationalISDNNumber("2.5.4.25", "RFC 4519"), + /** */ + registeredAddress("2.5.4.26", "RFC 4519"), + /** */ + destinationIndicator("2.5.4.27", "RFC 4519"), + /** */ + preferredDeliveryMethod("2.5.4.28", "RFC 4519"), + /** */ + member("2.5.4.31", "RFC 4519"), + /** */ + owner("2.5.4.32", "RFC 4519"), + /** */ + roleOccupant("2.5.4.33", "RFC 4519"), + /** */ + seeAlso("2.5.4.34", "RFC 4519"), + /** */ + userPassword("2.5.4.35", "RFC 4519"), + /** */ + userCertificate("2.5.4.36", "RFC 4523"), + /** */ + cACertificate("2.5.4.37", "RFC 4523"), + /** */ + authorityRevocationList("2.5.4.38", "RFC 4523"), + /** */ + certificateRevocationList("2.5.4.39", "RFC 4523"), + /** */ + crossCertificatePair("2.5.4.40", "RFC 4523"), + /** */ + name("2.5.4.41", "RFC 4519"), + /** */ + givenName("2.5.4.42", "RFC 4519"), + /** */ + initials("2.5.4.43", "RFC 4519"), + /** */ + generationQualifier("2.5.4.44", "RFC 4519"), + /** */ + x500UniqueIdentifier("2.5.4.45", "RFC 4519"), + /** */ + dnQualifier("2.5.4.46", "RFC 4519"), + /** */ + enhancedSearchGuide("2.5.4.47", "RFC 4519"), + /** */ + distinguishedName("2.5.4.49", "RFC 4519"), + /** */ + uniqueMember("2.5.4.50", "RFC 4519"), + /** */ + houseIdentifier("2.5.4.51", "RFC 4519"), + /** */ + supportedAlgorithms("2.5.4.52", "RFC 4523"), + /** */ + deltaRevocationList("2.5.4.53", "RFC 4523"), + /** */ + createTimestamp("2.5.18.1", "RFC 4512"), + /** */ + modifyTimestamp("2.5.18.2", "RFC 4512"), + /** */ + creatorsName("2.5.18.3", "RFC 4512"), + /** */ + modifiersName("2.5.18.4", "RFC 4512"), + /** */ + subschemaSubentry("2.5.18.10", "RFC 4512"), + /** */ + dITStructureRules("2.5.21.1", "RFC 4512"), + /** */ + dITContentRules("2.5.21.2", "RFC 4512"), + /** */ + matchingRules("2.5.21.4", "RFC 4512"), + /** */ + attributeTypes("2.5.21.5", "RFC 4512"), + /** */ + objectClasses("2.5.21.6", "RFC 4512"), + /** */ + nameForms("2.5.21.7", "RFC 4512"), + /** */ + matchingRuleUse("2.5.21.8", "RFC 4512"), + /** */ + structuralObjectClass("2.5.21.9", "RFC 4512"), + /** */ + governingStructureRule("2.5.21.10", "RFC 4512"), + /** */ + carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"), + /** */ + departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"), + /** */ + employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"), + /** */ + employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"), + /** */ + changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"), + /** */ + targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"), + /** */ + changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"), + /** */ + changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"), + /** */ + newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"), + /** */ + deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"), + /** */ + newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"), + /** */ + ref("2.16.840.1.113730.3.1.34", "RFC 3296"), + /** */ + changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"), + /** */ + preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"), + /** */ + userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"), + /** */ + userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"), + /** */ + displayName("2.16.840.1.113730.3.1.241", "RFC 2798"), + + // Sun memberOf + memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"), + + // KERBEROS (partial) + krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"), + + // RFC 2985 and RFC 3039 (partial) + dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"), + /** */ + placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"), + /** */ + gender("1.3.6.1.5.5.7.9.3", "RFC 2985"), + /** */ + countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"), + /** */ + countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"), + + // RFC 2307bis (partial) + /** */ + uidNumber("1.3.6.1.1.1.1.0", "RFC 2307bis"), + /** */ + gidNumber("1.3.6.1.1.1.1.1", "RFC 2307bis"), + /** */ + homeDirectory("1.3.6.1.1.1.1.3", "RFC 2307bis"), + /** */ + loginShell("1.3.6.1.1.1.1.4", "RFC 2307bis"), + /** */ + memberUid("1.3.6.1.1.1.1.12", "RFC 2307bis"), + + // + ; + + public final static String DN = "dn"; + + private final String oid, spec; + private final QName value; + + LdapAttr(String oid, String spec) { + this.oid = oid; + this.spec = spec; + this.value = new ContentName(LDAP_NAMESPACE_URI, name()); + } + + public QName qName() { + return value; + } + + @Override + public String getID() { + return oid; + } + + @Override + public String getSpec() { + return spec; + } + + @Deprecated + public String property() { + return get(); + } + + @Deprecated + public String qualified() { + return get(); + } + + /** #deprecated use {@link #qName()} instead. */ +// @Deprecated + public String get() { + return RuntimeNamespaceContext.getNamespaceContext().getPrefix(LDAP_NAMESPACE_URI) + ":" + name(); + } + + @Override + public final String toString() { + // must return the name + return name(); + } + + @Override + public String getNamespace() { + return LDAP_NAMESPACE_URI; + } + + @Override + public String getDefaultPrefix() { + return LDAP_DEFAULT_PREFIX; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapObj.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapObj.java new file mode 100644 index 000000000..061d1172d --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapObj.java @@ -0,0 +1,155 @@ +package org.argeo.api.acr.ldap; + +import static org.argeo.api.acr.ArgeoNamespace.LDAP_DEFAULT_PREFIX; +import static org.argeo.api.acr.ArgeoNamespace.LDAP_NAMESPACE_URI; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.QNamed; +import org.argeo.api.acr.RuntimeNamespaceContext; + +/** + * Standard LDAP object classes as per + * https://www.ldap.com/ldap- + * oid-reference + */ +public enum LdapObj implements QNamed, SpecifiedName { + account("0.9.2342.19200300.100.4.5", "RFC 4524"), + /** */ + document("0.9.2342.19200300.100.4.6", "RFC 4524"), + /** */ + room("0.9.2342.19200300.100.4.7", "RFC 4524"), + /** */ + documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"), + /** */ + domain("0.9.2342.19200300.100.4.13", "RFC 4524"), + /** */ + rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"), + /** */ + domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"), + /** */ + friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"), + /** */ + simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"), + /** */ + uidObject("1.3.6.1.1.3.1", "RFC 4519"), + /** */ + extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"), + /** */ + dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"), + /** */ + authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"), + /** */ + namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"), + /** */ + inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"), + /** */ + top("2.5.6.0", "RFC 4512"), + /** */ + alias("2.5.6.1", "RFC 4512"), + /** */ + country("2.5.6.2", "RFC 4519"), + /** */ + locality("2.5.6.3", "RFC 4519"), + /** */ + organization("2.5.6.4", "RFC 4519"), + /** */ + organizationalUnit("2.5.6.5", "RFC 4519"), + /** */ + person("2.5.6.6", "RFC 4519"), + /** */ + organizationalPerson("2.5.6.7", "RFC 4519"), + /** */ + organizationalRole("2.5.6.8", "RFC 4519"), + /** */ + groupOfNames("2.5.6.9", "RFC 4519"), + /** */ + residentialPerson("2.5.6.10", "RFC 4519"), + /** */ + applicationProcess("2.5.6.11", "RFC 4519"), + /** */ + device("2.5.6.14", "RFC 4519"), + /** */ + strongAuthenticationUser("2.5.6.15", "RFC 4523"), + /** */ + certificationAuthority("2.5.6.16", "RFC 4523"), + // /** Should be certificationAuthority-V2 */ + // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") { + // }, + /** */ + groupOfUniqueNames("2.5.6.17", "RFC 4519"), + /** */ + userSecurityInformation("2.5.6.18", "RFC 4523"), + /** */ + cRLDistributionPoint("2.5.6.19", "RFC 4523"), + /** */ + pkiUser("2.5.6.21", "RFC 4523"), + /** */ + pkiCA("2.5.6.22", "RFC 4523"), + /** */ + deltaCRL("2.5.6.23", "RFC 4523"), + /** */ + subschema("2.5.20.1", "RFC 4512"), + /** */ + ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"), + /** */ + changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"), + /** */ + inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"), + /** */ + referral("2.16.840.1.113730.3.2.6", "RFC 3296"), + + // RFC 2307bis (partial) + /** */ + posixAccount("1.3.6.1.1.1.2.0", "RFC 2307bis"), + /** */ + posixGroup("1.3.6.1.1.1.2.2", "RFC 2307bis"), + + // + ; + + private final String oid, spec; + private final QName value; + + private LdapObj(String oid, String spec) { + this.oid = oid; + this.spec = spec; + this.value = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, name()); + } + + public QName qName() { + return value; + } + + public String getOid() { + return oid; + } + + public String getSpec() { + return spec; + } + + @Deprecated + public String property() { + return get(); + } + + /** #deprecated use {@link #qName()} instead. */ +// @Deprecated + public String get() { + return RuntimeNamespaceContext.getNamespaceContext().getPrefix(LDAP_NAMESPACE_URI) + ":" + name(); + } + + @Override + public String getNamespace() { + return LDAP_NAMESPACE_URI; + } + + @Override + public String getDefaultPrefix() { + return LDAP_DEFAULT_PREFIX; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NamingUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NamingUtils.java new file mode 100644 index 000000000..88b76ecd2 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NamingUtils.java @@ -0,0 +1,106 @@ +package org.argeo.api.acr.ldap; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class NamingUtils { + /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */ + private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX") + .withZone(ZoneOffset.UTC); + + /** @return null if not parseable */ + public static Instant ldapDateToInstant(String ldapDate) { + try { + return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant(); + } catch (DateTimeParseException e) { + return null; + } + } + + /** @return null if not parseable */ + public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) { + try { + return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime(); + } catch (DateTimeParseException e) { + return null; + } + } + + public static Calendar ldapDateToCalendar(String ldapDate) { + OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate); + GregorianCalendar calendar = new GregorianCalendar(); + calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH)); + calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR)); + calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR)); + return calendar; + } + + public static String instantToLdapDate(ZonedDateTime instant) { + return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC)); + } + + public static String getQueryValue(Map> query, String key) { + if (!query.containsKey(key)) + return null; + List 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> queryToMap(URI uri) { + return queryToMap(uri.getQuery()); + } + + private static Map> queryToMap(String queryPart) { + try { + final Map> query_pairs = new LinkedHashMap>(); + 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()); + } + final String value = idx > 0 && pair.length() > idx + 1 + ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) + : null; + query_pairs.get(key).add(value); + } + return query_pairs; + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e); + } + } + + private NamingUtils() { + + } + +// public static void main(String args[]) { +// ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC); +// String str = utcLdapDate.format(now); +// System.out.println(str); +// utcLdapDate.parse(str); +// utcLdapDate.parse("19520512000000Z"); +// } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NodeOID.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NodeOID.java new file mode 100644 index 000000000..a68b6cb8b --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NodeOID.java @@ -0,0 +1,17 @@ +package org.argeo.api.acr.ldap; + +interface NodeOID { + String BASE = "1.3.6.1.4.1" + ".48308" + ".1"; + + // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308 + String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb"; + + // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308 + String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27"; + + // ATTRIBUTE TYPES + String ATTRIBUTE_TYPES = BASE + ".4"; + + // OBJECT CLASSES + String OBJECT_CLASSES = BASE + ".6"; +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/SpecifiedName.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/SpecifiedName.java new file mode 100644 index 000000000..19e724063 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/SpecifiedName.java @@ -0,0 +1,20 @@ +package org.argeo.api.acr.ldap; + +/** + * A name which has been specified and for which an id has been defined + * (typically an OID). + */ +interface SpecifiedName { + /** The name */ + String name(); + + /** An RFC or the URLof some specification */ + default String getSpec() { + return null; + } + + /** Typically an OID */ + default String getID() { + return getClass().getName() + "." + name(); + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java deleted file mode 100644 index 6bfc7cd5f..000000000 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.argeo.api.acr.spi; - -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import javax.xml.namespace.QName; - -import org.argeo.api.acr.Content; -import org.argeo.api.acr.CrName; - -public abstract class AbstractContent extends AbstractMap implements Content { - - /* - * ATTRIBUTES OPERATIONS - */ - protected abstract Iterable keys(); - - protected abstract void removeAttr(QName key); - - @Override - public Set> entrySet() { - Set> result = new AttrSet(); - return result; - } - - @Override - public Class getType(QName key) { - return String.class; - } - - @Override - public boolean isMultiple(QName key) { - return false; - } - - @Override - public Optional> getMultiple(QName key, Class clss) { - Object value = get(key); - if (value == null) - return null; - if (value instanceof List) { - try { - List res = (List) value; - return Optional.of(res); - } catch (ClassCastException e) { - List 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 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 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> { - - @Override - public Iterator> iterator() { - final Iterator keys = keys().iterator(); - Iterator> it = new Iterator>() { - - QName key = null; - - @Override - public boolean hasNext() { - return keys.hasNext(); - } - - @Override - public Entry next() { - key = keys.next(); - // TODO check type - Optional value = get(key, Object.class); - assert !value.isEmpty(); - AbstractMap.SimpleEntry entry = new SimpleEntry<>(key, value.get()); - return entry; - } - - @Override - public void remove() { - if (key != null) { - AbstractContent.this.removeAttr(key); - } else { - throw new IllegalStateException("Iteration has not started"); - } - } - - }; - return it; - } - - @Override - public int size() { - int count = 0; - for (QName key : keys()) { - count++; - } - return count; - } - - } -} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentNamespace.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentNamespace.java new file mode 100644 index 000000000..f91a177f4 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentNamespace.java @@ -0,0 +1,13 @@ +package org.argeo.api.acr.spi; + +import java.net.URL; + +/** A namespace and its default prefix, possibly with a schema definition. */ +public interface ContentNamespace { + String getDefaultPrefix(); + + String getNamespaceURI(); + + URL getSchemaResource(); + +} 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 index d83cf49c9..72aa162b3 100644 --- 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 @@ -1,9 +1,32 @@ package org.argeo.api.acr.spi; -import org.argeo.api.acr.Content; +import java.util.Iterator; -public interface ContentProvider { +import javax.xml.namespace.NamespaceContext; - Content get(ProvidedSession session, String mountPath, String relativePath); +public interface ContentProvider extends NamespaceContext { + + ProvidedContent get(ProvidedSession session, String relativePath); + + boolean exists(ProvidedSession session, String relativePath); + + String getMountPath(); + + /* + * NAMESPACE CONTEXT + */ + @Override + default String getPrefix(String namespaceURI) { + Iterator prefixes = getPrefixes(namespaceURI); + return prefixes.hasNext() ? prefixes.next() : null; + } + +// default ContentName parsePrefixedName(String nameWithPrefix) { +// return NamespaceUtils.parsePrefixedName(this, nameWithPrefix); +// } +// +// default String toPrefixedName(QName name) { +// return NamespaceUtils.toPrefixedName(this, name); +// } } 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 index d9fc781d0..e2807c0ef 100644 --- 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 @@ -2,8 +2,34 @@ package org.argeo.api.acr.spi; import org.argeo.api.acr.Content; +/** A {@link Content} implementation. */ public interface ProvidedContent extends Content { + final static String ROOT_PATH = "/"; + ProvidedSession getSession(); ContentProvider getProvider(); + + int getDepth(); + + /** + * An opaque ID which is guaranteed to uniquely identify this content within the + * session return by {@link #getSession()}. Typically used for UI. + */ + String getSessionLocalId(); + + default ProvidedContent getMountPoint(String relativePath) { + throw new UnsupportedOperationException("This content doe not support mount"); + } + + default ProvidedContent getContent(String path) { + Content fileNode; + if (path.startsWith(ROOT_PATH)) {// absolute + fileNode = getSession().get(path); + } else {// relative + String absolutePath = getPath() + '/' + path; + fileNode = getSession().get(absolutePath); + } + return (ProvidedContent) fileNode; + } } 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 index c3052c375..06ee43aa7 100644 --- 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 @@ -1,6 +1,17 @@ package org.argeo.api.acr.spi; +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentRepository; +/** A {@link ContentRepository} implementation. */ public interface ProvidedRepository extends ContentRepository { + void registerTypes(ContentNamespace... namespaces); + + ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types); + + boolean shouldMount(QName... types); + + void addProvider(ContentProvider provider); } 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 index f90d67475..5a538b57a 100644 --- 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 @@ -1,68 +1,45 @@ package org.argeo.api.acr.spi; -import java.util.Collections; import java.util.Iterator; -import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletionStage; -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; - -import org.argeo.api.acr.ContentNameSupplier; +import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.RuntimeNamespaceContext; -public interface ProvidedSession extends ContentSession, NamespaceContext { +/** A {@link ContentSession} implementation. */ +public interface ProvidedSession extends ContentSession { ProvidedRepository getRepository(); + CompletionStage onClose(); + + Content getMountPoint(String path); + + boolean isEditing(); + + void notifyModification(ProvidedContent content); + + UUID getUuid(); + +// Content getSessionRunDir(); + /* * NAMESPACE CONTEXT */ - /** @return the bound namespace or null if not found */ - String findNamespace(String prefix); - - // TODO find the default prefix? - Set findPrefixes(String namespaceURI); - - /** To be overridden for optimisation, as it will be called a lot */ - default String findPrefix(String namespaceURI) { - Set prefixes = findPrefixes(namespaceURI); - if (prefixes.isEmpty()) - return null; - return prefixes.iterator().next(); - } @Override - default String getNamespaceURI(String prefix) { - String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix); - if (namespaceURI != null) - return namespaceURI; - if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) - return XMLConstants.NULL_NS_URI; - namespaceURI = findNamespace(prefix); - if (namespaceURI != null) - return namespaceURI; - return XMLConstants.NULL_NS_URI; + default String getPrefix(String namespaceURI) { + return RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI); } @Override - default String getPrefix(String namespaceURI) { - String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI); - if (prefix != null) - return prefix; - if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) - return XMLConstants.DEFAULT_NS_PREFIX; - return findPrefix(namespaceURI); + default String getNamespaceURI(String prefix) { + return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix); } @Override default Iterator getPrefixes(String namespaceURI) { - Iterator 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 prefixes = findPrefixes(namespaceURI); - assert prefixes != null; - return prefixes.iterator(); + return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI); } - } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/ArrayTabularRow.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/ArrayTabularRow.java new file mode 100644 index 000000000..c0efe87ff --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/ArrayTabularRow.java @@ -0,0 +1,25 @@ +package org.argeo.api.acr.tabular; + +import java.util.List; + +/** Minimal tabular row wrapping an {@link Object} array */ +public class ArrayTabularRow implements TabularRow { + private final Object[] arr; + + public ArrayTabularRow(List objs) { + this.arr = objs.toArray(); + } + + public Object get(Integer col) { + return arr[col]; + } + + public int size() { + return arr.length; + } + + public Object[] toArray() { + return arr; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularColumn.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularColumn.java new file mode 100644 index 000000000..5b9bf239a --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularColumn.java @@ -0,0 +1,41 @@ +package org.argeo.api.acr.tabular; + +/** The column in a tabular content */ +public class TabularColumn { + private String name; + /** + * JCR types, see + * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html + * ?javax/jcr/PropertyType.html + */ + private Integer type; + + /** column with default type */ + public TabularColumn(String name) { + super(); + this.name = name; + } + + public TabularColumn(String name, Integer type) { + super(); + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularContent.java new file mode 100644 index 000000000..ae6eb4e2a --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularContent.java @@ -0,0 +1,14 @@ +package org.argeo.api.acr.tabular; + +import java.util.List; + +/** + * Content organized as a table, possibly with headers. Only JCR types are + * supported even though there is not direct dependency on JCR. + */ +public interface TabularContent { + /** The headers of this table or null is none available. */ + public List getColumns(); + + public TabularRowIterator read(); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRow.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRow.java new file mode 100644 index 000000000..5302cc04f --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRow.java @@ -0,0 +1,13 @@ +package org.argeo.api.acr.tabular; + +/** A row of tabular data */ +public interface TabularRow { + /** The value at this column index */ + public Object get(Integer col); + + /** The raw objects (direct references) */ + public Object[] toArray(); + + /** Number of columns */ + public int size(); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRowIterator.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRowIterator.java new file mode 100644 index 000000000..768c593ad --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRowIterator.java @@ -0,0 +1,12 @@ +package org.argeo.api.acr.tabular; + +import java.util.Iterator; + +/** Navigation of rows */ +public interface TabularRowIterator extends Iterator { + /** + * Current row number, has to be incremented by each call to next() ; starts at 0, will + * therefore be 1 for the first row returned. + */ + public Long getCurrentRowNumber(); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularWriter.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularWriter.java new file mode 100644 index 000000000..f1d555f6c --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularWriter.java @@ -0,0 +1,11 @@ +package org.argeo.api.acr.tabular; + + +/** Write to a tabular content */ +public interface TabularWriter { + /** Append a new row of data */ + public void appendRow(Object[] row); + + /** Finish persisting data and release resources */ + public void close(); +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/package-info.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/package-info.java new file mode 100644 index 000000000..06acbc587 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/package-info.java @@ -0,0 +1,2 @@ +/** Tabular format API. */ +package org.argeo.api.acr.tabular; \ No newline at end of file diff --git a/org.argeo.api.cli/.classpath b/org.argeo.api.cli/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.api.cli/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.api.cli/.project b/org.argeo.api.cli/.project new file mode 100644 index 000000000..8f5cd419b --- /dev/null +++ b/org.argeo.api.cli/.project @@ -0,0 +1,28 @@ + + + org.argeo.api.cli + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.api.cli/bnd.bnd b/org.argeo.api.cli/bnd.bnd new file mode 100644 index 000000000..e69de29bb diff --git a/org.argeo.api.cli/build.properties b/org.argeo.api.cli/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.api.cli/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java new file mode 100644 index 000000000..935247fee --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java @@ -0,0 +1,33 @@ +package org.argeo.api.cli; + +/** Exception thrown when the provided arguments are not correct. */ +public class CommandArgsException extends IllegalArgumentException { + private static final long serialVersionUID = -7271050747105253935L; + private String commandName; + private volatile CommandsCli commandsCli; + + public CommandArgsException(Exception cause) { + super(cause.getMessage(), cause); + } + + public CommandArgsException(String message) { + super(message); + } + + public String getCommandName() { + return commandName; + } + + public void setCommandName(String commandName) { + this.commandName = commandName; + } + + public CommandsCli getCommandsCli() { + return commandsCli; + } + + public void setCommandsCli(CommandsCli commandsCli) { + this.commandsCli = commandsCli; + } + +} diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java new file mode 100644 index 000000000..52c033433 --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java @@ -0,0 +1,35 @@ +package org.argeo.api.cli; + +import java.util.List; + +/** {@link RuntimeException} referring during a command run. */ +public class CommandRuntimeException extends RuntimeException { + private static final long serialVersionUID = 5595999301269377128L; + + private final DescribedCommand command; + private final List arguments; + + public CommandRuntimeException(Throwable e, DescribedCommand command, List arguments) { + this(null, e, command, arguments); + } + + public CommandRuntimeException(String message, DescribedCommand command, List arguments) { + this(message, null, command, arguments); + } + + public CommandRuntimeException(String message, Throwable e, DescribedCommand command, List 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 getArguments() { + return arguments; + } + +} diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java new file mode 100644 index 000000000..5bbfcfa07 --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java @@ -0,0 +1,157 @@ +package org.argeo.api.cli; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.MissingOptionException; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** Base class for a CLI managing sub commands. */ +public abstract class CommandsCli implements DescribedCommand { + private final String commandName; + private Map, ?>> commands = new TreeMap<>(); + + protected final Options options = new Options(); + + public CommandsCli(String commandName) { + this.commandName = commandName; + } + + @Override + public Object apply(List args) { + String cmd = null; + List newArgs = new ArrayList<>(); + boolean isHelpOption = false; + try { + CommandLineParser clParser = new DefaultParser(); + CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true); + List leftOvers = commonCl.getArgList(); + for (String arg : leftOvers) { + if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) { + isHelpOption = true; + // TODO break? + } + + if (!arg.startsWith("-") && cmd == null) { + cmd = arg; + } else { + newArgs.add(arg); + } + } + } catch (ParseException e) { + CommandArgsException cae = new CommandArgsException(e); + throw cae; + } + + Function, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand(); + + // --help option + if (!(function instanceof CommandsCli)) + if (function instanceof DescribedCommand command) + if (isHelpOption) { + throw new PrintHelpRequestException(cmd, this); +// StringWriter out = new StringWriter(); +// HelpCommand.printHelp(command, out); +// System.out.println(out.toString()); +// return null; + } + + if (function == null) + throw new IllegalArgumentException("Uknown command " + cmd); + try { + Object value = function.apply(newArgs); + return value != null ? value.toString() : null; + } catch (CommandArgsException e) { + if (e.getCommandName() == null) { + e.setCommandName(cmd); + e.setCommandsCli(this); + } + throw e; + } catch (IllegalArgumentException e) { + CommandArgsException cae = new CommandArgsException(e); + cae.setCommandName(cmd); + throw cae; + } + } + + @Override + public Options getOptions() { + return options; + } + + protected void addCommand(String cmd, Function, ?> function) { + commands.put(cmd, function); + + } + + @Override + public String getUsage() { + return "[command]"; + } + + protected void addCommandsCli(CommandsCli commandsCli) { + addCommand(commandsCli.getCommandName(), commandsCli); + commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli)); + } + + public String getCommandName() { + return commandName; + } + + public Set getSubCommands() { + return commands.keySet(); + } + + public Function, ?> getCommand(String command) { + return commands.get(command); + } + + public HelpCommand getHelpCommand() { + return (HelpCommand) getCommand(HelpCommand.HELP); + } + + public Function, String> getDefaultCommand() { + return getHelpCommand(); + } + + /** In order to implement quickly a main method. */ + public static void mainImpl(CommandsCli cli, String[] args) { + try { + cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli)); + Object output = cli.apply(Arrays.asList(args)); + if (output != null) + System.out.println(output); + System.exit(0); + } catch (PrintHelpRequestException e) { + StringWriter out = new StringWriter(); + HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out); + System.out.println(out.toString()); + } catch (CommandArgsException e) { + System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage()); + Throwable cause = e.getCause(); + if (!(cause instanceof MissingOptionException)) + e.printStackTrace(); + if (e.getCommandName() != null) { + StringWriter out = new StringWriter(); + HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out); + System.err.println(out.toString()); + } else { + e.printStackTrace(); + } + System.exit(1); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java new file mode 100644 index 000000000..51cb2ceca --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java @@ -0,0 +1,60 @@ +package org.argeo.api.cli; + +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** A command that can be described. */ +public interface DescribedCommand extends Function, T> { + default Options getOptions() { + return new Options(); + } + + String getDescription(); + + default String getUsage() { + return null; + } + + default String getExamples() { + return null; + } + + default CommandLine toCommandLine(List args) { + try { + DefaultParser parser = new DefaultParser(); + return parser.parse(getOptions(), args.toArray(new String[args.size()])); + } catch (ParseException e) { + throw new CommandArgsException(e); + } + } + + /** In order to implement quickly a main method. */ + public static void mainImpl(DescribedCommand command, String[] args) { + try { + Object output = command.apply(Arrays.asList(args)); + System.out.println(output); + System.exit(0); + } catch (PrintHelpRequestException e) { + StringWriter out = new StringWriter(); + HelpCommand.printHelp(command, out); + System.out.println(out.toString()); + System.exit(1); + } catch (IllegalArgumentException e) { + StringWriter out = new StringWriter(); + HelpCommand.printHelp(command, out); + System.err.println(out.toString()); + System.exit(1); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + +} diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java new file mode 100644 index 000000000..cd51d7695 --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java @@ -0,0 +1,164 @@ +package org.argeo.api.cli; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.function.Function; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; + +/** A special command that can describe {@link DescribedCommand}. */ +public class HelpCommand implements DescribedCommand { + /** + * System property forcing the root command to this value (typically the name of + * a script). + */ + public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand"; + + final static String HELP = "help"; + final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build(); + + private CommandsCli commandsCli; + private CommandsCli parentCommandsCli; + + // Help formatting + private static int helpWidth = 80; + private static int helpLeftPad = 4; + private static int helpDescPad = 20; + + public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) { + super(); + this.parentCommandsCli = parentCommandsCli; + this.commandsCli = commandsCli; + } + + @Override + public String apply(List args) { + StringWriter out = new StringWriter(); + + if (args.size() == 0) {// overview + printHelp(commandsCli, out); + } else { + String cmd = args.get(0); + Function, ?> 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, ?> function) { + if (function instanceof DescribedCommand) { + return ((DescribedCommand) function).getDescription(); + } else { + return function.toString(); + } + } + + public String getCommandUsage(String cmd, DescribedCommand command) { + String commandCall = getCommandCall(commandsCli) + " " + cmd; + assert command != null; + if (command != null && command.getUsage() != null) { + commandCall = commandCall + " " + command.getUsage(); + } + return commandCall; + } + + @Override + public String getDescription() { + return "Shows this help or describes a command"; + } + + @Override + public String getUsage() { + return "[command]"; + } + + public CommandsCli getParentCommandsCli() { + return parentCommandsCli; + } + + protected String getCommandCall(CommandsCli commandsCli) { + HelpCommand hc = commandsCli.getHelpCommand(); + if (hc.getParentCommandsCli() != null) { + return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName(); + } else { + String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY); + if (rootCommand != null) + return rootCommand; + return commandsCli.getCommandName(); + } + } + + public static void printHelp(DescribedCommand command, StringWriter out) { + String usage = "java " + command.getClass().getName() + + (command.getUsage() != null ? " " + command.getUsage() : ""); + HelpFormatter formatter = new HelpFormatter(); + Options options = command.getOptions(); + options.addOption(HelpCommand.HELP_OPTION); + formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad, + helpDescPad, command.getExamples(), false); + + } + + public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) { + if (commandName == null) { + printHelp(commandsCli, out); + return; + } + DescribedCommand command = (DescribedCommand) commandsCli.getCommand(commandName); + String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command); + HelpFormatter formatter = new HelpFormatter(); + Options options = command.getOptions(); + options.addOption(HelpCommand.HELP_OPTION); + formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad, + helpDescPad, command.getExamples(), false); + + } + + public static void printHelp(CommandsCli commandsCli, StringWriter out) { + out.append(commandsCli.getDescription()).append('\n'); + String leftPad = spaces(helpLeftPad); + for (String cmd : commandsCli.getSubCommands()) { + Function, ?> function = commandsCli.getCommand(cmd); + assert function != null; + out.append(leftPad); + out.append(cmd); + // TODO deal with long commands + out.append(spaces(helpDescPad - cmd.length())); + out.append(getShortDescription(function)); + out.append('\n'); + } + } + + private static String spaces(int count) { + // Java 11 + // return " ".repeat(count); + if (count <= 0) + return ""; + else { + StringBuilder sb = new StringBuilder(count); + for (int i = 0; i < count; i++) + sb.append(' '); + return sb.toString(); + } + } +} diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java b/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java new file mode 100644 index 000000000..017386b83 --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java @@ -0,0 +1,29 @@ +package org.argeo.api.cli; + +/** An exception indicating that help should be printed. */ +class PrintHelpRequestException extends RuntimeException { + + private static final long serialVersionUID = -9029122270660656639L; + + private String commandName; + private volatile CommandsCli commandsCli; + + public PrintHelpRequestException(String commandName, CommandsCli commandsCli) { + super(); + this.commandName = commandName; + this.commandsCli = commandsCli; + } + + public static long getSerialversionuid() { + return serialVersionUID; + } + + public String getCommandName() { + return commandName; + } + + public CommandsCli getCommandsCli() { + return commandsCli; + } + +} diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java b/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java new file mode 100644 index 000000000..114fd02d4 --- /dev/null +++ b/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java @@ -0,0 +1,2 @@ +/** Command line API. */ +package org.argeo.api.cli; \ No newline at end of file diff --git a/org.argeo.api.cms/.classpath b/org.argeo.api.cms/.classpath index e801ebfb4..81fe078c2 100644 --- a/org.argeo.api.cms/.classpath +++ b/org.argeo.api.cms/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.api.cms/bnd.bnd b/org.argeo.api.cms/bnd.bnd index 51c4e663c..36cde6410 100644 --- a/org.argeo.api.cms/bnd.bnd +++ b/org.argeo.api.cms/bnd.bnd @@ -1,4 +1,6 @@ Import-Package: \ -javax.security.* +javax.transaction.xa,\ +javax.security.*,\ +org.osgi.service.useradmin,\ Export-Package: org.argeo.api.cms.* \ No newline at end of file diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java deleted file mode 100644 index 30b3d8100..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.api.cms; - -/** A 2D size. */ -public class Cms2DSize { - private Integer width; - private Integer height; - - public Cms2DSize() { - - } - - public Cms2DSize(Integer width, Integer height) { - super(); - this.width = width; - this.height = height; - } - - public Integer getWidth() { - return width; - } - - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - - public void setHeight(Integer height) { - this.height = height; - } - -} 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 index 761191e5d..b180fff75 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java @@ -1,9 +1,13 @@ package org.argeo.api.cms; +import java.util.Map; import java.util.Set; +import org.argeo.api.cms.ux.CmsTheme; +import org.argeo.api.cms.ux.CmsUi; + /** An extensible user interface base on the CMS backend. */ -public interface CmsApp { +public interface CmsApp extends CmsEventSubscriber { /** * If {@link CmsUi#setData(String, Object)} is set with this property, it * indicates a different UI (typically with another theming. The {@link CmsApp} @@ -13,6 +17,8 @@ public interface CmsApp { */ final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name"; + final static String CONTEXT_NAME_PROPERTY = "argeo.cms.app.contextName"; + Set getUiNames(); CmsUi initUi(Object uiParent); @@ -28,4 +34,12 @@ public interface CmsApp { void addCmsAppListener(CmsAppListener listener); void removeCmsAppListener(CmsAppListener listener); + + CmsContext getCmsContext(); + + @Override + default void onEvent(String topic, Map properties) { + } + + } 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 index decea3550..31ec8be50 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java @@ -1,5 +1,6 @@ package org.argeo.api.cms; +import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; @@ -21,6 +22,18 @@ public enum CmsAuth { return new LoginContext(getLoginContextName(), callbackHandler); } + public LoginContext newLoginContext(Subject subject, CallbackHandler callbackHandler) throws LoginException { + return new LoginContext(getLoginContextName(), subject, callbackHandler); + } + + public LoginContext newLoginContext(Subject subject) throws LoginException { + return new LoginContext(getLoginContextName(), subject); + } + + public LoginContext newLoginContext() throws LoginException { + return new LoginContext(getLoginContextName()); + } + /* * LOGIN CONTEXTS */ 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 index 8fe9846f4..bae874b16 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java @@ -35,6 +35,12 @@ public interface CmsConstants { String GUESTS_WORKSPACE = "guests"; String PUBLIC_WORKSPACE = "public"; String SECURITY_WORKSPACE = "security"; + String MIGRATION_WORKSPACE = "migration"; + + /* + * ACR CONVENTIONS + */ + String SRV_BASE = "/srv"; /* * BASE DNs @@ -49,17 +55,18 @@ public interface CmsConstants { /* * RESERVED ROLES */ - String ROLES_BASEDN = "ou=roles,ou=node"; + String NODE_BASEDN = "ou=node"; + String SYSTEM_ROLES_BASEDN = "ou=roles," + NODE_BASEDN; String TOKENS_BASEDN = "ou=tokens,ou=node"; - String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN; - String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN; - String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN; + String ROLE_ADMIN = "cn=admin," + SYSTEM_ROLES_BASEDN; + String ROLE_USER_ADMIN = "cn=userAdmin," + SYSTEM_ROLES_BASEDN; + String ROLE_DATA_ADMIN = "cn=dataAdmin," + SYSTEM_ROLES_BASEDN; // Special system groups that cannot be edited: // user U anonymous = everyone - String ROLE_USER = "cn=user," + ROLES_BASEDN; - String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN; + String ROLE_USER = "cn=user," + SYSTEM_ROLES_BASEDN; + String ROLE_ANONYMOUS = "cn=anonymous," + SYSTEM_ROLES_BASEDN; // Account lifecycle - String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN; + String ROLE_REGISTERING = "cn=registering," + SYSTEM_ROLES_BASEDN; /* * PATHS @@ -68,6 +75,7 @@ public interface CmsConstants { String PATH_JCR = "/jcr"; String PATH_FILES = "/files"; // String PATH_JCR_PUB = "/pub"; + String PATH_API_ACR = "/api/acr"; /* * FILE SYSTEMS @@ -80,33 +88,17 @@ public interface CmsConstants { String NODE_SERVICE = NODE; /* - * INIT FRAMEWORK PROPERTIES + * COMPONENT PROPERTIES */ - String NODE_INIT = "argeo.node.init"; - String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale"; - String I18N_LOCALES = "argeo.i18n.locales"; - // Node Security - String ROLES_URI = "argeo.node.roles.uri"; - String TOKENS_URI = "argeo.node.tokens.uri"; - /** URI to an LDIF file or LDAP server used as initialization or backend */ - String USERADMIN_URIS = "argeo.node.useradmin.uris"; - // Transaction manager - String TRANSACTION_MANAGER = "argeo.node.transaction.manager"; - String TRANSACTION_MANAGER_SIMPLE = "simple"; - String TRANSACTION_MANAGER_BITRONIX = "bitronix"; - // Node - /** Properties configuring the node repository */ - String NODE_REPO_PROP_PREFIX = "argeo.node.repo."; - /** Additional standalone repositories, related to data models. */ - String NODE_REPOS_PROP_PREFIX = "argeo.node.repos."; - // HTTP - String HTTP_PORT = "org.osgi.service.http.port"; - String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure"; - /** - * The HTTP header used to convey the DN of a client verified by a reverse - * proxy. Typically SSL_CLIENT_S_DN for Apache. + String CONTEXT_PATH = "context.path"; + String CONTEXT_PUBLIC = "context.public"; + String EVENT_TOPICS = "event.topics"; + String ACR_MOUNT_PATH = "acr.mount.path"; + + /* + * FILE SYSTEM */ - String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn"; + String CMS_FS_SCHEME = "cms"; /* * PIDs 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 index fa26b253a..6ad0f512c 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java @@ -2,6 +2,9 @@ package org.argeo.api.cms; import java.util.List; import java.util.Locale; +import java.util.UUID; + +import javax.security.auth.Subject; /** * A logical view on this CMS instance, independently of a particular launch or @@ -20,7 +23,15 @@ public interface CmsContext { Long getAvailableSince(); - /** Mark this group as a workgroup */ void createWorkgroup(String groupDn); + + /** Get the CMS session of this subject. */ + CmsSession getCmsSession(Subject subject); + + CmsEventBus getCmsEventBus(); + + /** A new time based {@link UUID} (v1) using the current time */ + UUID timeUUID(); + } 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 index 5893d2ec5..d557816cb 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java @@ -1,11 +1,9 @@ package org.argeo.api.cms; -import java.util.Dictionary; - /** A configured node deployment. */ public interface CmsDeployment { - void addFactoryDeployConfig(String factoryPid, Dictionary props); - - Dictionary getProps(String factoryPid, String cn); +// void addFactoryDeployConfig(String factoryPid, Dictionary props); +// +// Dictionary getProps(String factoryPid, String cn); } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java deleted file mode 100644 index 2deca018e..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.argeo.api.cms; - -/** Abstraction of a simple edition life cycle. */ -public interface CmsEditable { - - /** Whether the calling thread can edit, the value is immutable */ - public Boolean canEdit(); - - public Boolean isEditing(); - - public void startEditing(); - - public void stopEditing(); - - public static CmsEditable NON_EDITABLE = new CmsEditable() { - - @Override - public void stopEditing() { - } - - @Override - public void startEditing() { - } - - @Override - public Boolean isEditing() { - return false; - } - - @Override - public Boolean canEdit() { - return false; - } - }; - - public static CmsEditable ALWAYS_EDITING = new CmsEditable() { - - @Override - public void stopEditing() { - } - - @Override - public void startEditing() { - } - - @Override - public Boolean isEditing() { - return true; - } - - @Override - public Boolean canEdit() { - return true; - } - }; - -} 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 index b5dccbe9c..6ec70feba 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java @@ -1,5 +1,7 @@ package org.argeo.api.cms; +import org.argeo.api.cms.ux.CmsView; + /** * Can be applied to {@link Enum}s in order to define events used by * {@link CmsView#sendEvent(String, java.util.Map)}. @@ -8,12 +10,11 @@ public interface CmsEvent { String name(); default String topic() { - return getTopicBase() + "/" + name(); + return getTopicBase() + "." + name(); } - default String getTopicBase() { - return "argeo/cms"; + default String getTopicBase() { + return "argeo.cms"; } - } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java new file mode 100644 index 000000000..bb8a78206 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java @@ -0,0 +1,12 @@ +package org.argeo.api.cms; + +import java.util.Map; + +public interface CmsEventBus { + void sendEvent(String topic, Map event); + + void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber); + + void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventSubscriber.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventSubscriber.java new file mode 100644 index 000000000..9ca5eaa38 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventSubscriber.java @@ -0,0 +1,8 @@ +package org.argeo.api.cms; + +import java.util.Map; + +public interface CmsEventSubscriber { + + void onEvent(String topic, Map properties); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java deleted file mode 100644 index 8c637b8cb..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.api.cms; - -import java.io.InputStream; - -/** Read and write access to images. */ -public interface CmsImageManager { - /** Load image in control */ - public Boolean load(M node, V control, Cms2DSize size); - - /** @return (0,0) if not available */ - public Cms2DSize getImageSize(M node); - - /** - * The related <img> tag, with src, width and height set. - * - * @return null if not available - */ - public String getImageTag(M node); - - /** - * The related <img> tag, with url, width and height set. Caller must - * close the tag (or add additional attributes). - * - * @return null if not available - */ - public StringBuilder getImageTagBuilder(M node, Cms2DSize size); - - /** - * Returns the remotely accessible URL of the image (registering it if - * needed) @return null if not available - */ - public String getImageUrl(M node); - -// public Binary getImageBinary(Node node) throws RepositoryException; - -// public Image getSwtImage(Node node) throws RepositoryException; - - /** @return URL */ - public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType); - - @Deprecated - default String uploadImage(M uploadFolder, String fileName, InputStream in) { - System.err.println("Context must be provided to " + CmsImageManager.class.getName()); - return uploadImage(null, uploadFolder, fileName, in, null); - } -} 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 index 3454dfc61..96a09a91b 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java @@ -1,16 +1,45 @@ package org.argeo.api.cms; import java.lang.System.Logger; -import java.lang.System.Logger.Level; +import java.text.MessageFormat; import java.util.Objects; import java.util.function.Supplier; /** - * A Commons Logging / SLF4J style logging utilities wrapping a standard Java - * platform {@link Logger}. + * A Commons Logging / SLF4J style logging utilities usually wrapping a standard + * Java platform {@link Logger}, but which can fallback to other mechanism, if a + * system logger is not available. */ public interface CmsLog { - Logger getLogger(); + /* + * SYSTEM LOGGER STYLE METHODS + */ + boolean isLoggable(Level level); + + void log(Level level, Supplier msgSupplier, Throwable thrown); + + void log(Level level, String msg, Throwable thrown); + + void log(Level level, String format, Object... params); + + default void log(Level level, String msg) { + log(level, msg, (Throwable) null); + } + + default void log(Level level, Supplier msgSupplier) { + log(level, msgSupplier, (Throwable) null); + } + + default void log(Level level, Object obj) { + Objects.requireNonNull(obj); + log(level, obj.toString()); + } + + /* + * SLF4j / COMMONS LOGGING STYLE METHODS + */ + @Deprecated + CmsLog getLogger(); default boolean isDebugEnabled() { return getLogger().isLoggable(Level.DEBUG); @@ -172,6 +201,30 @@ public interface CmsLog { getLogger().log(Level.ERROR, format, arguments); } + /** + * Exact mapping of ${java.lang.System.Logger.Level}, in case it is not + * available. + */ + public static enum Level { + ALL(Integer.MIN_VALUE), // + TRACE(400), // + DEBUG(500), // + INFO(800), // + WARNING(900), // + ERROR(1000), // + OFF(Integer.MAX_VALUE); // + + final int severity; + + private Level(int severity) { + this.severity = severity; + } + + public final int getSeverity() { + return severity; + } + } + /* * STATIC UTILITIES */ @@ -181,23 +234,123 @@ public interface CmsLog { } static CmsLog getLog(String name) { - Logger logger = System.getLogger(Objects.requireNonNull(name)); - return new LoggerWrapper(logger); + if (isSystemLoggerAvailable) { + return new SystemCmsLog(name); + } else { // typically Android + return new FallBackCmsLog(); + } } - /** A trivial implementation wrapping a platform logger. */ - static class LoggerWrapper implements CmsLog { - private final Logger logger; + static final boolean isSystemLoggerAvailable = isSystemLoggerAvailable(); - LoggerWrapper(Logger logger) { - this.logger = logger; + static boolean isSystemLoggerAvailable() { + try { + Logger logger = System.getLogger(CmsLog.class.getName()); + logger.log(java.lang.System.Logger.Level.TRACE, () -> "System logger is available."); + return true; + } catch (NoSuchMethodError | NoClassDefFoundError e) {// Android + return false; } + } +} - @Override - public Logger getLogger() { - return logger; +/** + * Uses {@link System.Logger}, should be used on proper implementations of the + * Java platform. + */ +class SystemCmsLog implements CmsLog { + private final Logger logger; + + SystemCmsLog(String name) { + logger = System.getLogger(name); + } + + @Override + public boolean isLoggable(Level level) { + return logger.isLoggable(convertSystemLevel(level)); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + logger.log(convertSystemLevel(level), msgSupplier, thrown); + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + logger.log(convertSystemLevel(level), msg, thrown); + } + + java.lang.System.Logger.Level convertSystemLevel(Level level) { + switch (level.severity) { + case Integer.MIN_VALUE: + return java.lang.System.Logger.Level.ALL; + case 400: + return java.lang.System.Logger.Level.TRACE; + case 500: + return java.lang.System.Logger.Level.DEBUG; + case 800: + return java.lang.System.Logger.Level.INFO; + case 900: + return java.lang.System.Logger.Level.WARNING; + case 1000: + return java.lang.System.Logger.Level.ERROR; + case Integer.MAX_VALUE: + return java.lang.System.Logger.Level.OFF; + default: + throw new IllegalArgumentException("Unexpected value: " + level.severity); } + } + + @Override + public void log(Level level, String format, Object... params) { + logger.log(convertSystemLevel(level), format, params); + } + @Override + public CmsLog getLogger() { + return this; } +}; +/** Dummy fallback for non-standard platforms such as Android. */ +class FallBackCmsLog implements CmsLog { + @Override + public boolean isLoggable(Level level) { + return level.getSeverity() >= 800;// INFO and higher + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + if (isLoggable(level)) + if (thrown != null || level.getSeverity() >= 900) { + System.err.println(msgSupplier.get()); + thrown.printStackTrace(); + } else { + System.out.println(msgSupplier.get()); + } + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (isLoggable(level)) + if (thrown != null || level.getSeverity() >= 900) { + System.err.println(msg); + thrown.printStackTrace(); + } else { + System.out.println(msg); + } + } + + @Override + public void log(Level level, String format, Object... params) { + if (format == null) + return; + String msg = MessageFormat.format(format, params); + log(level, msg); + } + + @Override + public CmsLog getLogger() { + return this; + } } 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 index ea9d10ba2..dda1dac1f 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java @@ -5,7 +5,6 @@ import java.util.Locale; import java.util.UUID; import java.util.function.Consumer; -import javax.naming.ldap.LdapName; import javax.security.auth.Subject; /** An authenticated user session. */ @@ -18,7 +17,7 @@ public interface CmsSession { String getUserRole(); - LdapName getUserDn(); + String getUserDn(); String getLocalId(); @@ -40,4 +39,8 @@ public interface CmsSession { void registerView(String uid, Object view); void addOnCloseCallback(Consumer onClose); + + public static boolean hasCmsSession(Subject subject) { + return !subject.getPrivateCredentials(CmsSessionId.class).isEmpty(); + } } 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 index ed8698fca..181e4b9c6 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java @@ -1,9 +1,24 @@ package org.argeo.api.cms; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; + /** A running node process. */ public interface CmsState { String getHostname(); Long getAvailableSince(); + UUID getUuid(); + + String getDeployProperty(String property); + + /** + * A list of size of the max count for this property, with null values when the + * property is not set, or an empty list (size 0) if this property is unknown. + */ + List getDeployProperties(String property); + + Path getDataPath(String relativePath); } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java deleted file mode 100644 index 8444e2fc5..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.api.cms; - -/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */ -public interface CmsStyle { - String name(); - - /** @deprecated use {@link #style()} instead. */ - @Deprecated - default String toStyleClass() { - return style(); - } - - default String style() { - String classPrefix = getClassPrefix(); - return "".equals(classPrefix) ? name() : classPrefix + "-" + name(); - } - - default String getClassPrefix() { - return ""; - } - -} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java deleted file mode 100644 index 50c3b1f25..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.argeo.api.cms; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Set; - -/** A CMS theme which can be applied to web apps as well as desktop apps. */ -public interface CmsTheme { - /** Unique ID of this theme. */ - String getThemeId(); - - /** - * Load a resource as an input stream, base don its relative path, or - * null if not found - */ - InputStream getResourceAsStream(String resourceName) throws IOException; - - /** Relative paths to standard web CSS. */ - Set getWebCssPaths(); - - /** Relative paths to RAP specific CSS. */ - Set getRapCssPaths(); - - /** Relative paths to SWT specific CSS. */ - Set getSwtCssPaths(); - - /** Relative paths to images such as icons. */ - Set getImagesPaths(); - - /** Relative paths to fonts. */ - Set getFontsPaths(); - - /** Tags to be added to the header section of the HTML page. */ - String getHtmlHeaders(); - - /** The HTML body to use. */ - String getBodyHtml(); - - /** The default icon size (typically the smallest). */ - Integer getDefaultIconSize(); - - /** Loads one of the relative path provided by the other methods. */ - InputStream loadPath(String path) throws IOException; - -} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java deleted file mode 100644 index fd91c6e34..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.api.cms; - -public interface CmsUi { - Object getData(String key); - void setData(String key, Object value); - -} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java deleted file mode 100644 index c7ca1e90c..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.argeo.api.cms; - -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; - -import javax.security.auth.login.LoginContext; - -/** Provides interaction with the CMS system. */ -public interface CmsView { - final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid"; - // String KEY = "org.argeo.cms.ui.view"; - - String getUid(); - - UxContext getUxContext(); - - // NAVIGATION - void navigateTo(String state); - - // SECURITY - void authChange(LoginContext loginContext); - - void logout(); - - // void registerCallbackHandler(CallbackHandler callbackHandler); - - // SERVICES - void exception(Throwable e); - - CmsImageManager getImageManager(); - - boolean isAnonymous(); - - /** - * Send an event to this topic. Does nothing by default., but if implemented it - * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties. - */ - default void sendEvent(String topic, Map properties) { - - } - - /** - * Convenience methods for when {@link #sendEvent(String, Map)} only requires - * one single parameter. - */ - default void sendEvent(String topic, String param, Object value) { - Map properties = new HashMap<>(); - properties.put(param, value); - sendEvent(topic, properties); - } - - default void applyStyles(Object widget) { - - } - - default T doAs(PrivilegedAction action) { - throw new UnsupportedOperationException(); - } - - default Void runAs(Runnable runnable) { - return doAs(new PrivilegedAction() { - - @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 getUiContext(Class clss) { - return (T) getData(clss.getName()); - } - - default void setUiContext(Class 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 index bc12bcbe2..70c50ee9b 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java @@ -2,6 +2,8 @@ package org.argeo.api.cms; import java.security.Principal; +import javax.security.auth.Subject; + /** Allows to modify any data. */ public final class DataAdminPrincipal implements Principal { private final String name = CmsConstants.ROLE_DATA_ADMIN; @@ -26,4 +28,7 @@ public final class DataAdminPrincipal implements Principal { return name.toString(); } + public static boolean isDataAdmin(Subject subject) { + return !subject.getPrincipals(DataAdminPrincipal.class).isEmpty(); + } } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java deleted file mode 100644 index c1aa6006c..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.api.cms; - -import java.util.function.BiFunction; - -/** - * Stateless UI part creator. Takes a parent view (V) and a model context (M) in - * order to create a view part (W) which can then be further configured. Such - * object can be used as services and reference other part of the model which - * are relevant for all created UI part. - */ -@FunctionalInterface -public interface MvcProvider extends BiFunction { - W createUiPart(V parent, M context); - - /** - * Whether this parent view is supported. - * - * @return true by default. - */ - default boolean isViewSupported(V parent) { - return true; - } - - /** - * Whether this context is supported. - * - * @return true by default. - */ - default boolean isModelSupported(M context) { - return true; - } - - default W apply(V parent, M context) { - if (!isViewSupported(parent)) - throw new IllegalArgumentException("Parent view " + parent + "is not supported."); - if (!isModelSupported(context)) - throw new IllegalArgumentException("Model context " + context + "is not supported."); - return createUiPart(parent, context); - } - - default W createUiPart(V parent) { - return createUiPart(parent, null); - } -} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java deleted file mode 100644 index fb99178ee..000000000 --- a/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.api.cms; - -public interface UxContext { - boolean isPortrait(); - - boolean isLandscape(); - - boolean isSquare(); - - boolean isSmall(); - - /** - * Is a production environment (must be false by default, and be explicitly - * set during the CMS deployment). When false, it can activate additional UI - * capabilities in order to facilitate QA. - */ - boolean isMasterData(); -} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java new file mode 100644 index 000000000..5d3a69575 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java @@ -0,0 +1,11 @@ +package org.argeo.api.cms.directory; + +import org.osgi.service.useradmin.Authorization; + +/** An authorisation to a CMS system. */ +public interface CmsAuthorization extends Authorization { + /** The role which did imply this role, null if a direct role. */ + default String getImplyingRole(String role) { + return null; + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsDirectory.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsDirectory.java new file mode 100644 index 000000000..f5b78ac45 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsDirectory.java @@ -0,0 +1,32 @@ +package org.argeo.api.cms.directory; + +import java.util.Optional; + +import org.argeo.api.cms.transaction.WorkControl; + +/** An information directory (typically LDAP). */ +public interface CmsDirectory extends HierarchyUnit { + String getName(); + + /** Whether this directory is read only. */ + boolean isReadOnly(); + + /** Whether this directory is disabled. */ + boolean isDisabled(); + + /** The realm (typically Kerberos) of this directory. */ + Optional getRealm(); + + /** Sets the transaction control used by this directory when editing. */ + void setTransactionControl(WorkControl transactionControl); + + /* + * HIERARCHY + */ + + /** The hierarchy unit at this path. */ + HierarchyUnit getHierarchyUnit(String path); + + /** Create a new hierarchy unit. */ + HierarchyUnit createHierarchyUnit(String path); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java new file mode 100644 index 000000000..410d391ba --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java @@ -0,0 +1,8 @@ +package org.argeo.api.cms.directory; + +import org.osgi.service.useradmin.Group; + +/** A group in a user directroy. */ +public interface CmsGroup extends Group, CmsUser { +// List getMemberNames(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java new file mode 100644 index 000000000..f8f40a1a6 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java @@ -0,0 +1,10 @@ +package org.argeo.api.cms.directory; + +import org.osgi.service.useradmin.User; + +/** + * An entity with credentials which can log in to a system. Can be a real person + * or not. + */ +public interface CmsUser extends User { +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java new file mode 100644 index 000000000..7693f6710 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java @@ -0,0 +1,125 @@ +package org.argeo.api.cms.directory; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.xml.namespace.QName; + +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +/** + * Provide method interfaces to manage user concepts without accessing directly + * the userAdmin. + */ +public interface CmsUserManager { + Map getKnownBaseDns(boolean onlyWritable); + + Set getUserDirectories(); + + // CurrentUser + /** Returns the e-mail of the current logged in user */ + String getMyMail(); + + // Other users + /** Returns a {@link User} given a username */ + CmsUser getUser(String username); + + /** Can be a group or a user */ + String getUserDisplayName(String dn); + + /** Can be a group or a user */ + String getUserMail(String dn); + + /** Lists all roles of the given user */ + String[] getUserRoles(String dn); + + /** Checks if the passed user belongs to the passed role */ + boolean isUserInRole(String userDn, String roleDn); + + // Search + /** Returns a filtered list of roles */ + Role[] getRoles(String filter); + + /** Recursively lists users in a given group. */ + Set listUsersInGroup(String groupDn, String filter); + + /** Search among groups including system roles and users if needed */ + List listGroups(String filter, boolean includeUsers, boolean includeSystemRoles); + +// /** +// * Lists functional accounts, that is users with regular access to the system +// * under this functional hierarchy unit (which probably have technical direct +// * sub hierarchy units), excluding groups which are not explicitly users. +// */ +// Set listAccounts(HierarchyUnit hierarchyUnit, boolean deep); + + /* + * EDITION + */ + /** Creates a new user. */ + CmsUser createUser(String username, Map properties, Map credentials); + + /** Created a new group. */ + CmsGroup createGroup(String dn); + + /** Creates a group. */ + CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName); + + /** Creates a new system role. */ + CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole); + + /** Add additional object classes to this role. */ + void addObjectClasses(Role role, Set objectClasses, Map additionalProperties); + + /** Add additional object classes to this hierarchy unit. */ + void addObjectClasses(HierarchyUnit hierarchyUnit, Set objectClasses, + Map additionalProperties); + + /** Add a member to this group. */ + void addMember(CmsGroup group, Role role); + + /** Remove a member from this group. */ + void removeMember(CmsGroup group, Role role); + + void edit(Runnable action); + + /* MISCELLANEOUS */ + /** Returns the dn of a role given its local ID */ + String buildDefaultDN(String localId, int type); + + /** Exposes the main default domain name for this instance */ + String getDefaultDomainName(); + + /** + * Search for a {@link User} (might also be a group) whose uid or cn is equals + * to localId within the various user repositories defined in the current + * context. + */ + CmsUser getUserFromLocalId(String localId); + + void changeOwnPassword(char[] oldPassword, char[] newPassword); + + void resetPassword(String username, char[] newPassword); + + @Deprecated + String addSharedSecret(String username, int hours); + +// String addSharedSecret(String username, String authInfo, String authToken); + + void addAuthToken(String userDn, String token, Integer hours, String... roles); + + void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles); + + void expireAuthToken(String token); + + void expireAuthTokens(Subject subject); + + UserDirectory getDirectory(Role role); + + /** Create a new hierarchy unit. Does nothing if it already exists. */ + HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path); +} \ No newline at end of file diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/DirectoryDigestUtils.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/DirectoryDigestUtils.java new file mode 100644 index 000000000..dabcfe8ee --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/DirectoryDigestUtils.java @@ -0,0 +1,114 @@ +package org.argeo.api.cms.directory; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Arrays; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +/** Utilities around digests, mostly those related to passwords. */ +public class DirectoryDigestUtils { + public final static String PASSWORD_SCHEME_SHA = "SHA"; + public final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256"; + + public static byte[] sha1(byte[] bytes) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + digest.update(bytes); + byte[] checksum = digest.digest(); + return checksum; + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot SHA1 digest", e); + } + } + + public static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations, + Integer keyLength) { + try { + if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + byte[] bytes = charsToBytes(password); + digest.update(bytes); + return digest.digest(); + } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) { + KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength); + + SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + final int ITERATION_LENGTH = 4; + byte[] key = f.generateSecret(spec).getEncoded(); + byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length]; + byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray(); + if (iterationsArr.length < ITERATION_LENGTH) { + Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0); + System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length, + iterationsArr.length); + } else { + System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH); + } + System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length); + System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length); + return result; + } else { + throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme); + } + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalStateException("Cannot digest", e); + } + } + + public static char[] bytesToChars(Object obj) { + if (obj instanceof char[]) + return (char[]) obj; + if (!(obj instanceof byte[])) + throw new IllegalArgumentException(obj.getClass() + " is not a byte array"); + ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj); + CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer); + char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit()); + // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data + // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data + // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data + return res; + } + + public static byte[] charsToBytes(char[] chars) { + CharBuffer charBuffer = CharBuffer.wrap(chars); + ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); + byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); + // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data + // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data + return bytes; + } + + public static String sha1str(String str) { + byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8)); + return encodeHexString(hash); + } + + final private static char[] hexArray = "0123456789abcdef".toCharArray(); + + /** + * From + * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to + * -a-hex-string-in-java + */ + public static String encodeHexString(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + /** singleton */ + private DirectoryDigestUtils() { + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/HierarchyUnit.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/HierarchyUnit.java new file mode 100644 index 000000000..52509e854 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/HierarchyUnit.java @@ -0,0 +1,56 @@ +package org.argeo.api.cms.directory; + +import java.util.Dictionary; +import java.util.Locale; + +/** A unit within the high-level organisational structure of a directory. */ +public interface HierarchyUnit { + /** Name to use in paths. */ + String getHierarchyUnitName(); + + /** Name to use in UI. */ + String getHierarchyUnitLabel(Locale locale); + + /** + * The parent {@link HierarchyUnit}, or null if a + * {@link CmsDirectory}. + */ + HierarchyUnit getParent(); + + /** Direct children {@link HierarchyUnit}s. */ + Iterable getDirectHierarchyUnits(boolean functionalOnly); + + /** + * Whether this is an arbitrary named and placed {@link HierarchyUnit}. + * + * @return true if functional, false is technical + * (e.g. People, Groups, etc.) + */ + default boolean isFunctional() { + return isType(Type.FUNCTIONAL); + } + + boolean isType(Type type); + + /** A technical direct child. */ + HierarchyUnit getDirectChild(Type type); + + /** + * The base of this organisational unit within the hierarchy. This would + * typically be an LDAP base DN. + */ + String getBase(); + + /** The related {@link CmsDirectory}. */ + CmsDirectory getDirectory(); + + /** Its metadata (typically LDAP attributes). */ + Dictionary getProperties(); + + enum Type { + PEOPLE, // + GROUPS, // + ROLES, // + FUNCTIONAL; + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java new file mode 100644 index 000000000..1f0ecdf75 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java @@ -0,0 +1,17 @@ +package org.argeo.api.cms.directory; + +import org.osgi.service.useradmin.Role; + +/** Information about a user directory. */ +public interface UserDirectory extends CmsDirectory { + + HierarchyUnit getHierarchyUnit(Role role); + + Iterable getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep); + + String getRolePath(Role role); + + String getRoleSimpleName(Role role); + + Role getRoleByPath(String path); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/CryptoKeyring.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/CryptoKeyring.java new file mode 100644 index 000000000..454f8b4a9 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/CryptoKeyring.java @@ -0,0 +1,10 @@ +package org.argeo.api.cms.keyring; + +/** + * Marker interface for an advanced keyring based on cryptography. + */ +public interface CryptoKeyring extends Keyring { + public void changePassword(char[] oldPassword, char[] newPassword); + + public void unlock(char[] password); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/Keyring.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/Keyring.java new file mode 100644 index 000000000..efc9455eb --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/Keyring.java @@ -0,0 +1,26 @@ +package org.argeo.api.cms.keyring; + +import java.io.InputStream; + +/** + * Access to private (typically encrypted) data. The keyring is responsible for + * retrieving the necessary credentials. Experimental. This API may + * change. + */ +public interface Keyring { + /** + * Returns the confidential information as chars. Must ask for it if it is + * not stored. + */ + public char[] getAsChars(String path); + + /** + * Returns the confidential information as a stream. Must ask for it if it + * is not stored. + */ + public InputStream getAsStream(String path); + + public void set(String path, char[] arr); + + public void set(String path, InputStream in); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/PBEKeySpecCallback.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/PBEKeySpecCallback.java new file mode 100644 index 000000000..6a7ce19fb --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/PBEKeySpecCallback.java @@ -0,0 +1,63 @@ +package org.argeo.api.cms.keyring; + +import javax.crypto.spec.PBEKeySpec; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.PasswordCallback; + +/** + * All information required to set up a {@link PBEKeySpec} bar the password + * itself (use a {@link PasswordCallback}) + */ +public class PBEKeySpecCallback implements Callback { + private String secretKeyFactory; + private byte[] salt; + private Integer iterationCount; + /** Can be null for some algorithms */ + private Integer keyLength; + /** Can be null, will trigger secret key encryption if not */ + private String secretKeyEncryption; + + private String encryptedPasswordHashCipher; + private byte[] encryptedPasswordHash; + + public void set(String secretKeyFactory, byte[] salt, + Integer iterationCount, Integer keyLength, + String secretKeyEncryption) { + this.secretKeyFactory = secretKeyFactory; + this.salt = salt; + this.iterationCount = iterationCount; + this.keyLength = keyLength; + this.secretKeyEncryption = secretKeyEncryption; +// this.encryptedPasswordHashCipher = encryptedPasswordHashCipher; +// this.encryptedPasswordHash = encryptedPasswordHash; + } + + public String getSecretKeyFactory() { + return secretKeyFactory; + } + + public byte[] getSalt() { + return salt; + } + + public Integer getIterationCount() { + return iterationCount; + } + + public Integer getKeyLength() { + return keyLength; + } + + public String getSecretKeyEncryption() { + return secretKeyEncryption; + } + + public String getEncryptedPasswordHashCipher() { + return encryptedPasswordHashCipher; + } + + public byte[] getEncryptedPasswordHash() { + return encryptedPasswordHash; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/package-info.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/package-info.java new file mode 100644 index 000000000..7f61e09b0 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS reusable security components. */ +package org.argeo.api.cms.keyring; \ No newline at end of file diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/AbstractWorkingCopy.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/AbstractWorkingCopy.java new file mode 100644 index 000000000..928acad2c --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/AbstractWorkingCopy.java @@ -0,0 +1,48 @@ +package org.argeo.api.cms.transaction; + +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractWorkingCopy implements WorkingCopy { + private Map newData = new HashMap(); + private Map modifiedData = new HashMap(); + private Map deletedData = new HashMap(); + + protected abstract ID getId(DATA data); + + protected abstract ATTR cloneAttributes(DATA data); + + public void cleanUp() { + // clean collections + newData.clear(); + newData = null; + modifiedData.clear(); + modifiedData = null; + deletedData.clear(); + deletedData = null; + } + + public boolean noModifications() { + return newData.size() == 0 && modifiedData.size() == 0 && deletedData.size() == 0; + } + + public void startEditing(DATA user) { + ID id = getId(user); + if (modifiedData.containsKey(id)) + throw new IllegalStateException("Already editing " + id); + modifiedData.put(id, cloneAttributes(user)); + } + + public Map getNewData() { + return newData; + } + + public Map getDeletedData() { + return deletedData; + } + + public Map getModifiedData() { + return modifiedData; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/JtaStatusAdapter.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/JtaStatusAdapter.java new file mode 100644 index 000000000..2ba6c0dae --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/JtaStatusAdapter.java @@ -0,0 +1,61 @@ +package org.argeo.api.cms.transaction; + +/** JTA transaction status. */ +public class JtaStatusAdapter implements TransactionStatusAdapter { + private static final Integer STATUS_ACTIVE = 0; + private static final Integer STATUS_COMMITTED = 3; + private static final Integer STATUS_COMMITTING = 8; + private static final Integer STATUS_MARKED_ROLLBACK = 1; + private static final Integer STATUS_NO_TRANSACTION = 6; + private static final Integer STATUS_PREPARED = 2; + private static final Integer STATUS_PREPARING = 7; + private static final Integer STATUS_ROLLEDBACK = 4; + private static final Integer STATUS_ROLLING_BACK = 9; +// private static final Integer STATUS_UNKNOWN = 5; + + @Override + public Integer getActiveStatus() { + return STATUS_ACTIVE; + } + + @Override + public Integer getPreparingStatus() { + return STATUS_PREPARING; + } + + @Override + public Integer getMarkedRollbackStatus() { + return STATUS_MARKED_ROLLBACK; + } + + @Override + public Integer getPreparedStatus() { + return STATUS_PREPARED; + } + + @Override + public Integer getCommittingStatus() { + return STATUS_COMMITTING; + } + + @Override + public Integer getCommittedStatus() { + return STATUS_COMMITTED; + } + + @Override + public Integer getRollingBackStatus() { + return STATUS_ROLLING_BACK; + } + + @Override + public Integer getRolledBackStatus() { + return STATUS_ROLLEDBACK; + } + + @Override + public Integer getNoTransactionStatus() { + return STATUS_NO_TRANSACTION; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleRollbackException.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleRollbackException.java new file mode 100644 index 000000000..39ed9b9a0 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleRollbackException.java @@ -0,0 +1,15 @@ +package org.argeo.api.cms.transaction; + +/** Internal unchecked rollback exception. */ +class SimpleRollbackException extends RuntimeException { + private static final long serialVersionUID = 8055601819719780566L; + + public SimpleRollbackException() { + super(); + } + + public SimpleRollbackException(Throwable cause) { + super(cause); + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransaction.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransaction.java new file mode 100644 index 000000000..f2bb9078c --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransaction.java @@ -0,0 +1,160 @@ +package org.argeo.api.cms.transaction; + +import java.util.ArrayList; +import java.util.List; + +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +/** Simple implementation of an XA transaction. */ +class SimpleTransaction +//implements Transaction, Status +{ + private final Xid xid; + private T status; + private final List xaResources = new ArrayList(); + + private final SimpleTransactionManager transactionManager; + private TransactionStatusAdapter tsa; + + public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter tsa) { + this.tsa = tsa; + this.status = tsa.getActiveStatus(); + this.xid = new UuidXid(); + this.transactionManager = transactionManager; + } + + public synchronized void commit() +// throws RollbackException, HeuristicMixedException, HeuristicRollbackException, +// SecurityException, IllegalStateException, SystemException + { + status = tsa.getPreparingStatus(); + for (XAResource xaRes : xaResources) { + if (status.equals(tsa.getMarkedRollbackStatus())) + break; + try { + xaRes.prepare(xid); + } catch (XAException e) { + status = tsa.getMarkedRollbackStatus(); + error("Cannot prepare " + xaRes + " for " + xid, e); + } + } + if (status.equals(tsa.getMarkedRollbackStatus())) { + rollback(); + throw new SimpleRollbackException(); + } + status = tsa.getPreparedStatus(); + + status = tsa.getCommittingStatus(); + for (XAResource xaRes : xaResources) { + if (status.equals(tsa.getMarkedRollbackStatus())) + break; + try { + xaRes.commit(xid, false); + } catch (XAException e) { + status = tsa.getMarkedRollbackStatus(); + error("Cannot prepare " + xaRes + " for " + xid, e); + } + } + if (status.equals(tsa.getMarkedRollbackStatus())) { + rollback(); + throw new SimpleRollbackException(); + } + + // complete + status = tsa.getCommittedStatus(); + clearResources(XAResource.TMSUCCESS); + transactionManager.unregister(xid); + } + + public synchronized void rollback() +// throws IllegalStateException, SystemException + { + status = tsa.getRollingBackStatus(); + for (XAResource xaRes : xaResources) { + try { + xaRes.rollback(xid); + } catch (XAException e) { + error("Cannot rollback " + xaRes + " for " + xid, e); + } + } + + // complete + status = tsa.getRolledBackStatus(); + clearResources(XAResource.TMFAIL); + transactionManager.unregister(xid); + } + + public synchronized boolean enlistResource(XAResource xaRes) +// throws RollbackException, IllegalStateException, SystemException + { + if (xaResources.add(xaRes)) { + try { + xaRes.start(getXid(), XAResource.TMNOFLAGS); + return true; + } catch (XAException e) { + error("Cannot enlist " + xaRes, e); + return false; + } + } else + return false; + } + + public synchronized boolean delistResource(XAResource xaRes, int flag) +// throws IllegalStateException, SystemException + { + if (xaResources.remove(xaRes)) { + try { + xaRes.end(getXid(), flag); + } catch (XAException e) { + error("Cannot delist " + xaRes, e); + return false; + } + return true; + } else + return false; + } + + protected void clearResources(int flag) { + for (XAResource xaRes : xaResources) + try { + xaRes.end(getXid(), flag); + } catch (XAException e) { + error("Cannot end " + xaRes, e); + } + xaResources.clear(); + } + + protected void error(Object obj, Exception e) { + System.err.println(obj); + e.printStackTrace(); + } + + public synchronized T getStatus() +// throws SystemException + { + return status; + } + +// public void registerSynchronization(Synchronization sync) +// throws RollbackException, IllegalStateException, SystemException { +// throw new UnsupportedOperationException(); +// } + + public void setRollbackOnly() +// throws IllegalStateException, SystemException + { + status = tsa.getMarkedRollbackStatus(); + } + + @Override + public int hashCode() { + return xid.hashCode(); + } + + Xid getXid() { + return xid; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransactionManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransactionManager.java new file mode 100644 index 000000000..ee99ccb19 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransactionManager.java @@ -0,0 +1,214 @@ +package org.argeo.api.cms.transaction; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +/** + * Simple implementation of an XA transaction manager. + */ +public class SimpleTransactionManager +// implements TransactionManager, UserTransaction + implements WorkControl, WorkTransaction { + private ThreadLocal> current = new ThreadLocal>(); + + private Map> knownTransactions = Collections + .synchronizedMap(new HashMap>()); + private TransactionStatusAdapter tsa = new JtaStatusAdapter(); +// private SyncRegistry syncRegistry = new SyncRegistry(); + + /* + * WORK IMPLEMENTATION + */ + @Override + public T required(Callable 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 transaction = new SimpleTransaction(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 getTransaction() +// throws SystemException + { + return getCurrent(); + } + + protected SimpleTransaction getCurrent() +// throws SystemException + { + SimpleTransaction 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 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 suspend() +// throws SystemException + { + SimpleTransaction transaction = getCurrent(); + current.remove(); + return transaction; + } + +// public TransactionSynchronizationRegistry getTsr() { +// return syncRegistry; +// } +// +// private class SyncRegistry implements TransactionSynchronizationRegistry { +// @Override +// public Object getTransactionKey() { +// try { +// SimpleTransaction transaction = getCurrent(); +// if (transaction == null) +// return null; +// return getCurrent().getXid(); +// } catch (SystemException e) { +// throw new IllegalStateException("Cannot get transaction key", e); +// } +// } +// +// @Override +// public void putResource(Object key, Object value) { +// throw new UnsupportedOperationException(); +// } +// +// @Override +// public Object getResource(Object key) { +// throw new UnsupportedOperationException(); +// } +// +// @Override +// public void registerInterposedSynchronization(Synchronization sync) { +// throw new UnsupportedOperationException(); +// } +// +// @Override +// public int getTransactionStatus() { +// try { +// return getStatus(); +// } catch (SystemException e) { +// throw new IllegalStateException("Cannot get status", e); +// } +// } +// +// @Override +// public boolean getRollbackOnly() { +// try { +// return getStatus() == Status.STATUS_MARKED_ROLLBACK; +// } catch (SystemException e) { +// throw new IllegalStateException("Cannot get status", e); +// } +// } +// +// @Override +// public void setRollbackOnly() { +// try { +// getCurrent().setRollbackOnly(); +// } catch (Exception e) { +// throw new IllegalStateException("Cannot set rollback only", e); +// } +// } +// +// } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/TransactionStatusAdapter.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/TransactionStatusAdapter.java new file mode 100644 index 000000000..ab4effd55 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/TransactionStatusAdapter.java @@ -0,0 +1,22 @@ +package org.argeo.api.cms.transaction; + +/** Abstract the various approaches to represent transaction status. */ +public interface TransactionStatusAdapter { + T getActiveStatus(); + + T getPreparingStatus(); + + T getMarkedRollbackStatus(); + + T getPreparedStatus(); + + T getCommittingStatus(); + + T getCommittedStatus(); + + T getRollingBackStatus(); + + T getRolledBackStatus(); + + T getNoTransactionStatus(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/UuidXid.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/UuidXid.java new file mode 100644 index 000000000..83358a5ae --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/UuidXid.java @@ -0,0 +1,132 @@ +package org.argeo.api.cms.transaction; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.UUID; + +import javax.transaction.xa.Xid; + +/** + * Implementation of {@link Xid} based on {@link UUID}, using max significant + * bits as global transaction id, and least significant bits as branch + * qualifier. + */ +public class UuidXid implements Xid, Serializable { + private static final long serialVersionUID = -5380531989917886819L; + public final static int FORMAT = (int) serialVersionUID; + + private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE; + + private final int format; + private final byte[] globalTransactionId; + private final byte[] branchQualifier; + private final String uuid; + private final int hashCode; + + public UuidXid() { + this(UUID.randomUUID()); + } + + public UuidXid(UUID uuid) { + this.format = FORMAT; + this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits()); + this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits()); + this.uuid = uuid.toString(); + this.hashCode = uuid.hashCode(); + } + + public UuidXid(Xid xid) { + this(xid.getFormatId(), xid.getGlobalTransactionId(), xid + .getBranchQualifier()); + } + + private UuidXid(int format, byte[] globalTransactionId, + byte[] branchQualifier) { + this.format = format; + this.globalTransactionId = globalTransactionId; + this.branchQualifier = branchQualifier; + this.uuid = bytesToUUID(globalTransactionId, branchQualifier) + .toString(); + this.hashCode = uuid.hashCode(); + } + + @Override + public int getFormatId() { + return format; + } + + @Override + public byte[] getGlobalTransactionId() { + return Arrays.copyOf(globalTransactionId, globalTransactionId.length); + } + + @Override + public byte[] getBranchQualifier() { + return Arrays.copyOf(branchQualifier, branchQualifier.length); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof UuidXid) { + UuidXid that = (UuidXid) obj; + return Arrays.equals(globalTransactionId, that.globalTransactionId) + && Arrays.equals(branchQualifier, that.branchQualifier); + } + if (obj instanceof Xid) { + Xid that = (Xid) obj; + return Arrays.equals(globalTransactionId, + that.getGlobalTransactionId()) + && Arrays + .equals(branchQualifier, that.getBranchQualifier()); + } + return uuid.equals(obj.toString()); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return new UuidXid(format, globalTransactionId, branchQualifier); + } + + @Override + public String toString() { + return uuid; + } + + public UUID asUuid() { + return bytesToUUID(globalTransactionId, branchQualifier); + } + + public static byte[] uuidToBytes(long bits) { + ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG); + buffer.putLong(0, bits); + return buffer.array(); + } + + public static UUID bytesToUUID(byte[] most, byte[] least) { + if (most.length < BYTES_PER_LONG) + most = Arrays.copyOf(most, BYTES_PER_LONG); + if (least.length < BYTES_PER_LONG) + least = Arrays.copyOf(least, BYTES_PER_LONG); + ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG); + buffer.put(most, 0, BYTES_PER_LONG); + buffer.put(least, 0, BYTES_PER_LONG); + buffer.flip(); + return new UUID(buffer.getLong(), buffer.getLong()); + } + + // public static void main(String[] args) { + // UUID uuid = UUID.randomUUID(); + // System.out.println(uuid); + // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()), + // uuidToBytes(uuid.getLeastSignificantBits())); + // System.out.println(uuid); + // } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkContext.java new file mode 100644 index 000000000..5493dde91 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkContext.java @@ -0,0 +1,11 @@ +package org.argeo.api.cms.transaction; + +import javax.transaction.xa.XAResource; + +/** + * A minimalistic interface similar to OSGi transaction context in order to + * register XA resources. + */ +public interface WorkContext { + void registerXAResource(XAResource resource, String recoveryId); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkControl.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkControl.java new file mode 100644 index 000000000..de03150c5 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkControl.java @@ -0,0 +1,15 @@ +package org.argeo.api.cms.transaction; + +import java.util.concurrent.Callable; + +/** + * A minimalistic interface inspired by OSGi transaction control in order to + * commit units of work externally. + */ +public interface WorkControl { + T required(Callable work); + + void setRollbackOnly(); + + WorkContext getWorkContext(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkTransaction.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkTransaction.java new file mode 100644 index 000000000..39c188d54 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkTransaction.java @@ -0,0 +1,15 @@ +package org.argeo.api.cms.transaction; + +/** + * A minimalistic interface inspired by JTA user transaction in order to commit + * units of work externally. + */ +public interface WorkTransaction { + void begin(); + + void commit(); + + void rollback(); + + boolean isNoTransactionStatus(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopy.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopy.java new file mode 100644 index 000000000..c79423c8c --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopy.java @@ -0,0 +1,18 @@ +package org.argeo.api.cms.transaction; + +import java.util.Map; + +public interface WorkingCopy { + void startEditing(DATA user); + + boolean noModifications(); + + void cleanUp(); + + Map getNewData(); + + Map getDeletedData(); + + Map getModifiedData(); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyProcessor.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyProcessor.java new file mode 100644 index 000000000..9e7c9e187 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyProcessor.java @@ -0,0 +1,11 @@ +package org.argeo.api.cms.transaction; + +public interface WorkingCopyProcessor> { + void prepare(WC wc); + + void commit(WC wc); + + void rollback(WC wc); + + WC newWorkingCopy(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyXaResource.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyXaResource.java new file mode 100644 index 000000000..16b08c287 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyXaResource.java @@ -0,0 +1,138 @@ +package org.argeo.api.cms.transaction; + +import java.util.HashMap; +import java.util.Map; + +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +/** {@link XAResource} for a user directory being edited. */ +public class WorkingCopyXaResource> implements XAResource { + private final WorkingCopyProcessor processor; + + private Map workingCopies = new HashMap(); + private Xid editingXid = null; + private int transactionTimeout = 0; + + public WorkingCopyXaResource(WorkingCopyProcessor processor) { + this.processor = processor; + } + + @Override + public synchronized void start(Xid xid, int flags) throws XAException { + if (editingXid != null) + throw new IllegalStateException("Already editing " + editingXid); + WC wc = workingCopies.put(xid, processor.newWorkingCopy()); + if (wc != null) + throw new IllegalStateException("There is already a working copy for " + xid); + this.editingXid = xid; + } + + @Override + public void end(Xid xid, int flags) throws XAException { + checkXid(xid); + } + + private WC wc(Xid xid) { + return workingCopies.get(xid); + } + + public synchronized WC wc() { + if (editingXid == null) + return null; + WC wc = workingCopies.get(editingXid); + if (wc == null) + throw new IllegalStateException("No working copy found for " + editingXid); + return wc; + } + + private synchronized void cleanUp(Xid xid) { + WC wc = workingCopies.get(xid); + if (wc != null) { + wc.cleanUp(); + workingCopies.remove(xid); + } + editingXid = null; + } + + @Override + public int prepare(Xid xid) throws XAException { + checkXid(xid); + WC wc = wc(xid); + if (wc.noModifications()) + return XA_RDONLY; + try { + processor.prepare(wc); + } catch (Exception e) { + e.printStackTrace(); + throw new XAException(XAException.XAER_RMERR); + } + return XA_OK; + } + + @Override + public void commit(Xid xid, boolean onePhase) throws XAException { + try { + checkXid(xid); + WC wc = wc(xid); + if (wc.noModifications()) + return; + if (onePhase) + processor.prepare(wc); + processor.commit(wc); + } catch (Exception e) { + e.printStackTrace(); + throw new XAException(XAException.XAER_RMERR); + } finally { + cleanUp(xid); + } + } + + @Override + public void rollback(Xid xid) throws XAException { + try { + checkXid(xid); + processor.rollback(wc(xid)); + } catch (Exception e) { + e.printStackTrace(); + throw new XAException(XAException.XAER_RMERR); + } finally { + cleanUp(xid); + } + } + + @Override + public void forget(Xid xid) throws XAException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSameRM(XAResource xares) throws XAException { + return xares == this; + } + + @Override + public Xid[] recover(int flag) throws XAException { + return new Xid[0]; + } + + @Override + public int getTransactionTimeout() throws XAException { + return transactionTimeout; + } + + @Override + public boolean setTransactionTimeout(int seconds) throws XAException { + transactionTimeout = seconds; + return true; + } + + private void checkXid(Xid xid) throws XAException { + if (xid == null) + throw new XAException(XAException.XAER_OUTSIDE); + if (!xid.equals(xid)) + throw new XAException(XAException.XAER_NOTA); + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/XAResourceProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/XAResourceProvider.java new file mode 100644 index 000000000..904fb5f48 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/XAResourceProvider.java @@ -0,0 +1,7 @@ +package org.argeo.api.cms.transaction; + +import javax.transaction.xa.XAResource; + +public interface XAResourceProvider { + XAResource getXaResource(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/package-info.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/package-info.java new file mode 100644 index 000000000..bbb9212bc --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/package-info.java @@ -0,0 +1,2 @@ +/** Minimalistic and partial XA transaction manager implementation. */ +package org.argeo.api.cms.transaction; \ No newline at end of file diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java new file mode 100644 index 000000000..1ec753a18 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java @@ -0,0 +1,38 @@ +package org.argeo.api.cms.ux; + +/** A 2D size. */ +public class Cms2DSize { + private Integer width; + private Integer height; + + public Cms2DSize() { + } + + public Cms2DSize(Integer width, Integer height) { + super(); + this.width = width; + this.height = height; + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + @Override + public String toString() { + return Cms2DSize.class.getSimpleName() + "[" + width + "," + height + "]"; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditable.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditable.java new file mode 100644 index 000000000..94ed24eb5 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditable.java @@ -0,0 +1,79 @@ +package org.argeo.api.cms.ux; + +/** Abstraction of a simple edition life cycle. */ +public interface CmsEditable { + + /** Whether the calling thread can edit, the value is immutable */ + Boolean canEdit(); + + Boolean isEditing(); + + void startEditing(); + + void stopEditing(); + + void addCmsEditionListener(CmsEditionListener listener); + + void removeCmsEditionListener(CmsEditionListener listener); + + static CmsEditable NON_EDITABLE = new CmsEditable() { + + @Override + public void stopEditing() { + } + + @Override + public void startEditing() { + } + + @Override + public Boolean isEditing() { + return false; + } + + @Override + public Boolean canEdit() { + return false; + } + + @Override + public void addCmsEditionListener(CmsEditionListener listener) { + } + + @Override + public void removeCmsEditionListener(CmsEditionListener listener) { + } + + }; + + static CmsEditable ALWAYS_EDITING = new CmsEditable() { + + @Override + public void stopEditing() { + } + + @Override + public void startEditing() { + } + + @Override + public Boolean isEditing() { + return true; + } + + @Override + public Boolean canEdit() { + return true; + } + + @Override + public void addCmsEditionListener(CmsEditionListener listener) { + } + + @Override + public void removeCmsEditionListener(CmsEditionListener listener) { + } + + }; + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionEvent.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionEvent.java new file mode 100644 index 000000000..e1765abab --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionEvent.java @@ -0,0 +1,29 @@ +package org.argeo.api.cms.ux; + +import java.util.EventObject; + +/** Notify of the edition lifecycle */ +public class CmsEditionEvent extends EventObject { + private static final long serialVersionUID = 950914736016693110L; + + public final static Integer START_EDITING = 0; + public final static Integer STOP_EDITING = 1; + + private final Integer type; + private final CmsEditable cmsEditable; + + public CmsEditionEvent(Object source, Integer type, CmsEditable cmsEditable) { + super(source); + this.type = type; + this.cmsEditable = cmsEditable; + } + + public Integer getType() { + return type; + } + + public CmsEditable getCmsEditable() { + return cmsEditable; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionListener.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionListener.java new file mode 100644 index 000000000..e05c16880 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionListener.java @@ -0,0 +1,7 @@ +package org.argeo.api.cms.ux; + +public interface CmsEditionListener { + void editionStarted(CmsEditionEvent e); + + void editionStopped(CmsEditionEvent e); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsIcon.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsIcon.java new file mode 100644 index 000000000..d4f86b207 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsIcon.java @@ -0,0 +1,10 @@ +package org.argeo.api.cms.ux; + +/** + * Marker interface to be applied to {@link Enum}s in order to find or generate + * icons. + */ +public interface CmsIcon { + String name(); + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java new file mode 100644 index 000000000..1ec54a9d9 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java @@ -0,0 +1,46 @@ +package org.argeo.api.cms.ux; + +import java.io.InputStream; + +/** Read and write access to images. */ +public interface CmsImageManager { + /** Load image in control */ + public Boolean load(M node, V control, Cms2DSize size); + + /** @return (0,0) if not available */ + public Cms2DSize getImageSize(M node); + + /** + * The related <img> tag, with src, width and height set. + * + * @return null if not available + */ + public String getImageTag(M node); + + /** + * The related <img> tag, with url, width and height set. Caller must + * close the tag (or add additional attributes). + * + * @return null if not available + */ + public StringBuilder getImageTagBuilder(M node, Cms2DSize size); + + /** + * Returns the remotely accessible URL of the image (registering it if + * needed) @return null if not available + */ + public String getImageUrl(M node); + +// public Binary getImageBinary(Node node) throws RepositoryException; + +// public Image getSwtImage(Node node) throws RepositoryException; + + /** @return URL */ + public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType); + + @Deprecated + default String uploadImage(M uploadFolder, String fileName, InputStream in) { + System.err.println("Context must be provided to " + CmsImageManager.class.getName()); + return uploadImage(null, uploadFolder, fileName, in, null); + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsStyle.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsStyle.java new file mode 100644 index 000000000..e3f2e771d --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsStyle.java @@ -0,0 +1,22 @@ +package org.argeo.api.cms.ux; + +/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */ +public interface CmsStyle { + String name(); + + /** @deprecated use {@link #style()} instead. */ + @Deprecated + default String toStyleClass() { + return style(); + } + + default String style() { + String classPrefix = getClassPrefix(); + return "".equals(classPrefix) ? name() : classPrefix + "-" + name(); + } + + default String getClassPrefix() { + return ""; + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsTheme.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsTheme.java new file mode 100644 index 000000000..3a4a78ed3 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsTheme.java @@ -0,0 +1,51 @@ +package org.argeo.api.cms.ux; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; + +/** A CMS theme which can be applied to web apps as well as desktop apps. */ +public interface CmsTheme { + /** Unique ID of this theme. */ + String getThemeId(); + + /** + * Load a resource as an input stream, base don its relative path, or + * null if not found + */ + InputStream getResourceAsStream(String resourceName) throws IOException; + + /** Relative paths to standard web CSS. */ + Set getWebCssPaths(); + + /** Relative paths to RAP specific CSS. */ + Set getRapCssPaths(); + + /** Relative paths to SWT specific CSS. */ + Set getSwtCssPaths(); + + /** Relative paths to images such as icons. */ + Set getImagesPaths(); + + /** Relative paths to fonts. */ + Set getFontsPaths(); + + /** Tags to be added to the header section of the HTML page. */ + String getHtmlHeaders(); + + /** The HTML body to use. */ + String getBodyHtml(); + + /** The default icon size (typically the smallest). */ + default int getDefaultIconSize() { + return getSmallIconSize(); + } + + int getSmallIconSize(); + + int getBigIconSize(); + + /** Loads one of the relative path provided by the other methods. */ + InputStream loadPath(String path) throws IOException; + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java new file mode 100644 index 000000000..2103e4989 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java @@ -0,0 +1,10 @@ +package org.argeo.api.cms.ux; + +/** The actual implementation of a user interface, using a given technology. */ +public interface CmsUi { + Object getData(String key); + + void setData(String key, Object value); + + CmsView getCmsView(); +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java new file mode 100644 index 000000000..15b6a5dc7 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java @@ -0,0 +1,94 @@ +package org.argeo.api.cms.ux; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; + +import javax.security.auth.login.LoginContext; + +import org.argeo.api.cms.CmsSession; + +/** Provides interaction with the CMS system. */ +public interface CmsView { + final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid"; + // String KEY = "org.argeo.cms.ui.view"; + + String getUid(); + + UxContext getUxContext(); + + // NAVIGATION + void navigateTo(String state); + + // SECURITY + void authChange(LoginContext loginContext); + + void logout(); + + // void registerCallbackHandler(CallbackHandler callbackHandler); + + // SERVICES + void exception(Throwable e); + + CmsImageManager getImageManager(); + + boolean isAnonymous(); + + /** + * Send an event to this topic. Does nothing by default., but if implemented it + * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties. + */ + default void sendEvent(String topic, Map properties) { + + } + + /** + * Convenience methods for when {@link #sendEvent(String, Map)} only requires + * one single parameter. + */ + default void sendEvent(String topic, String param, Object value) { + Map properties = new HashMap<>(); + properties.put(param, value); + sendEvent(topic, properties); + } + + default void applyStyles(Object widget) { + + } + + /** + * Make sure that this action is executed with the proper subject and in a + * proper thread. + */ + T doAs(Callable action); + + default void runAs(Runnable runnable) { + doAs(Executors.callable(runnable)); + } + + default void stateChanged(String state, String title) { + } + + default CmsSession getCmsSession() { + throw new UnsupportedOperationException(); + } + + default Object getData(String key) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + default T getUiContext(Class clss) { + return (T) getData(clss.getName()); + } + + default void setUiContext(Class clss, T instance) { + setData(clss.getName(), instance); + } + + default void setData(String key, Object value) { + throw new UnsupportedOperationException(); + } + +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/MvcProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/MvcProvider.java new file mode 100644 index 000000000..3556edecf --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/MvcProvider.java @@ -0,0 +1,45 @@ +package org.argeo.api.cms.ux; + +import java.util.function.BiFunction; + +/** + * Stateless UI part creator. Takes a parent view (V) and a model context (M) in + * order to create a view part (W) which can then be further configured. Such + * object can be used as services and reference other part of the model which + * are relevant for all created UI part. + */ +@FunctionalInterface +@Deprecated +public interface MvcProvider extends BiFunction { + W createUiPart(V parent, M context); + + /** + * Whether this parent view is supported. + * + * @return true by default. + */ + default boolean isViewSupported(V parent) { + return true; + } + + /** + * Whether this context is supported. + * + * @return true by default. + */ + default boolean isModelSupported(M context) { + return true; + } + + default W apply(V parent, M context) { + if (!isViewSupported(parent)) + throw new IllegalArgumentException("Parent view " + parent + "is not supported."); + if (!isModelSupported(context)) + throw new IllegalArgumentException("Model context " + context + "is not supported."); + return createUiPart(parent, context); + } + + default W createUiPart(V parent) { + return createUiPart(parent, null); + } +} diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/UxContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/UxContext.java new file mode 100644 index 000000000..adb41d366 --- /dev/null +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/UxContext.java @@ -0,0 +1,18 @@ +package org.argeo.api.cms.ux; + +public interface UxContext { + boolean isPortrait(); + + boolean isLandscape(); + + boolean isSquare(); + + boolean isSmall(); + + /** + * Is a production environment (must be false by default, and be explicitly + * set during the CMS deployment). When false, it can activate additional UI + * capabilities in order to facilitate QA. + */ + boolean isMasterData(); +} diff --git a/org.argeo.api.register/.classpath b/org.argeo.api.register/.classpath new file mode 100644 index 000000000..4199cd3a3 --- /dev/null +++ b/org.argeo.api.register/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.argeo.api.register/.project b/org.argeo.api.register/.project new file mode 100644 index 000000000..a3164c111 --- /dev/null +++ b/org.argeo.api.register/.project @@ -0,0 +1,28 @@ + + + org.argeo.api.register + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/org.argeo.api.register/META-INF/.gitignore b/org.argeo.api.register/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/org.argeo.api.register/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/org.argeo.api.register/bnd.bnd b/org.argeo.api.register/bnd.bnd new file mode 100644 index 000000000..bcebf3722 --- /dev/null +++ b/org.argeo.api.register/bnd.bnd @@ -0,0 +1,3 @@ +Import-Package: org.osgi.*;version=0.0.0,\ +!org.apache.commons.logging,\ +* diff --git a/org.argeo.api.register/build.properties b/org.argeo.api.register/build.properties new file mode 100644 index 000000000..ae2abc5ff --- /dev/null +++ b/org.argeo.api.register/build.properties @@ -0,0 +1 @@ +source.. = src/ \ No newline at end of file diff --git a/org.argeo.api.register/src/org/argeo/api/register/Component.java b/org.argeo.api.register/src/org/argeo/api/register/Component.java new file mode 100644 index 000000000..f4676d9cf --- /dev/null +++ b/org.argeo.api.register/src/org/argeo/api/register/Component.java @@ -0,0 +1,333 @@ +package org.argeo.api.register; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * A wrapper for an object, whose dependencies and life cycle can be managed. + */ +public class Component implements Supplier, Comparable> { + + private final I instance; + + private final Runnable init; + private final Runnable close; + + private final Map, PublishedType> types; + private final Set> dependencies; + private final Map properties; + + private CompletableFuture activationStarted = null; + private CompletableFuture activated = null; + + private CompletableFuture deactivationStarted = null; + private CompletableFuture deactivated = null; + + // internal + private Set> dependants = new HashSet<>(); + + private RankingKey rankingKey; + + Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set> dependencies, + Set> classes, Map properties) { + assert instance != null; + assert init != null; + assert close != null; + assert dependencies != null; + assert classes != null; + + this.instance = instance; + this.init = init; + this.close = close; + + // types + Map, PublishedType> types = new HashMap<>(classes.size()); + for (Class clss : classes) { +// if (!clss.isAssignableFrom(instance.getClass())) +// throw new IllegalArgumentException( +// "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName()); + types.put(clss, new PublishedType<>(this, clss)); + } + this.types = Collections.unmodifiableMap(types); + + // dependencies + this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies)); + for (Dependency dependency : this.dependencies) { + dependency.setDependantComponent(this); + } + + // deactivated by default + deactivated = CompletableFuture.completedFuture(null); + deactivationStarted = CompletableFuture.completedFuture(null); + + // TODO check whether context is active, so that we start right away + prepareNextActivation(); + + long serviceId = register.register(this); + Map props = new HashMap<>(properties); + props.put(RankingKey.SERVICE_ID, serviceId); + this.properties = Collections.unmodifiableMap(props); + rankingKey = new RankingKey(properties); + } + + private void prepareNextActivation() { + activationStarted = new CompletableFuture(); + activated = activationStarted // + .thenComposeAsync(this::dependenciesActivated) // + .thenRun(this.init) // + .thenRun(() -> prepareNextDeactivation()); + } + + private void prepareNextDeactivation() { + deactivationStarted = new CompletableFuture(); + deactivated = deactivationStarted // + .thenComposeAsync(this::dependantsDeactivated) // + .thenRun(this.close) // + .thenRun(() -> prepareNextActivation()); + } + + CompletableFuture getActivated() { + return activated; + } + + CompletableFuture 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 dependenciesActivated(Void v) { + Set> constraints = new HashSet<>(this.dependencies.size()); + for (Dependency dependency : this.dependencies) { + CompletableFuture dependencyActivated = dependency.publisherActivated() // + .thenCompose(dependency::set); + constraints.add(dependencyActivated); + } + return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()])); + } + + CompletableFuture dependantsDeactivated(Void v) { + Set> constraints = new HashSet<>(this.dependants.size()); + for (Dependency dependant : this.dependants) { + CompletableFuture dependantDeactivated = dependant.dependantDeactivated() // + .thenCompose(dependant::unset); + constraints.add(dependantDeactivated); + } + CompletableFuture dependantsDeactivated = CompletableFuture + .allOf(constraints.toArray(new CompletableFuture[constraints.size()])); + return dependantsDeactivated; + + } + + void addDependant(Dependency dependant) { + dependants.add(dependant); + } + + @Override + public I get() { + return instance; + } + + @SuppressWarnings("unchecked") + public PublishedType getType(Class clss) { + if (!types.containsKey(clss)) + throw new IllegalArgumentException(clss.getName() + " is not a type published by this component"); + return (PublishedType) types.get(clss); + } + + public boolean isPublishedType(Class clss) { + return types.containsKey(clss); + } + + public Map getProperties() { + return properties; + } + + @Override + public int compareTo(Component o) { + return rankingKey.compareTo(rankingKey); + } + + @Override + public int hashCode() { + Long serviceId = (Long) properties.get(RankingKey.SERVICE_ID); + if (serviceId != null) + return serviceId.intValue(); + else + return super.hashCode(); + } + + @Override + public String toString() { + List classes = new ArrayList<>(); + for (Class clss : types.keySet()) { + classes.add(clss.getName()); + } + return "Component " + classes + " " + properties + ""; + } + + /** A type which has been explicitly exposed by a component. */ + public static class PublishedType { + private Component component; + private Class clss; + + private CompletableFuture value; + + public PublishedType(Component component, Class clss) { + this.clss = clss; + this.component = component; + value = CompletableFuture.completedFuture((T) component.instance); + } + + public Component getPublisher() { + return component; + } + + public Class getType() { + return clss; + } + + public CompletionStage getValue() { + return value.minimalCompletionStage(); + } + } + + /** Builds a {@link Component}. */ + public static class Builder implements Supplier { + private final I instance; + + private Runnable init; + private Runnable close; + + private Set> dependencies = new HashSet<>(); + private Set> types = new HashSet<>(); + private final Map properties = new HashMap<>(); + + public Builder(I instance) { + this.instance = instance; + } + + public Component build(ComponentRegister register) { + // default values + if (types.isEmpty()) { + types.add(getInstanceClass()); + } + + if (init == null) + init = () -> { + }; + if (close == null) + close = () -> { + }; + + // instantiation + Component component = new Component(register, instance, init, close, dependencies, types, properties); + for (Dependency dependency : dependencies) { + dependency.type.getPublisher().addDependant(dependency); + } + return component; + } + + public Builder addType(Class clss) { + types.add(clss); + return this; + } + + public Builder addActivation(Runnable init) { + if (this.init != null) + throw new IllegalArgumentException("init method is already set"); + this.init = init; + return this; + } + + public Builder addDeactivation(Runnable close) { + if (this.close != null) + throw new IllegalArgumentException("close method is already set"); + this.close = close; + return this; + } + + public Builder addDependency(PublishedType type, Consumer set, Consumer unset) { + dependencies.add(new Dependency(type, set, unset)); + return this; + } + + public void addProperty(String key, Object value) { + if (properties.containsKey(key)) + throw new IllegalStateException("Key " + key + " is already set."); + properties.put(key, value); + } + + @Override + public I get() { + return instance; + } + + @SuppressWarnings("unchecked") + private Class getInstanceClass() { + return (Class) instance.getClass(); + } + + } + + static class Dependency { + private PublishedType type; + private Consumer set; + private Consumer unset; + + // live + Component dependantComponent; + CompletableFuture setStage; + CompletableFuture unsetStage; + + public Dependency(PublishedType types, Consumer set, Consumer unset) { + super(); + this.type = types; + this.set = set != null ? set : t -> { + }; + this.unset = unset != null ? unset : t -> { + }; + } + + // live + void setDependantComponent(Component component) { + this.dependantComponent = component; + } + + CompletableFuture publisherActivated() { + return type.getPublisher().activated.copy(); + } + + CompletableFuture dependantDeactivated() { + return dependantComponent.deactivated.copy(); + } + + CompletableFuture set(Void v) { + return type.value.thenAccept(set); + } + + CompletableFuture unset(Void v) { + return type.value.thenAccept(unset); + } + + } +} diff --git a/org.argeo.api.register/src/org/argeo/api/register/ComponentRegister.java b/org.argeo.api.register/src/org/argeo/api/register/ComponentRegister.java new file mode 100644 index 000000000..1bb9036f8 --- /dev/null +++ b/org.argeo.api.register/src/org/argeo/api/register/ComponentRegister.java @@ -0,0 +1,43 @@ +package org.argeo.api.register; + +import java.util.Map; +import java.util.SortedSet; +import java.util.function.Predicate; + +/** A register of components which can coordinate their activation. */ +public interface ComponentRegister { + long register(Component component); + + SortedSet> find(Class clss, Predicate> filter); + + default Component.PublishedType getSingleton(Class type) { + SortedSet> found = find(type, null); + if (found.size() == 0) + throw new IllegalStateException("No component found for " + type); + return found.first().getType(type); + } + + default T getObject(Class clss) { + SortedSet> found = find(clss, null); + if (found.size() == 0) + return null; + return found.first().get(); + } + + Component get(Object instance); + +// default PublishedType getType(Class clss) { +// SortedSet> components = find(clss, null); +// if (components.size() == 0) +// return null; +// return components.first().getType(clss); +// } + + void activate(); + + void deactivate(); + + boolean isActive(); + + void clear(); +} diff --git a/org.argeo.api.register/src/org/argeo/api/register/RankingKey.java b/org.argeo.api.register/src/org/argeo/api/register/RankingKey.java new file mode 100644 index 000000000..3886afe65 --- /dev/null +++ b/org.argeo.api.register/src/org/argeo/api/register/RankingKey.java @@ -0,0 +1,105 @@ +package org.argeo.api.register; + +import java.util.Map; +import java.util.Objects; + +/** + * Key used to classify and filter available components. + */ +public class RankingKey implements Comparable { + public final static String SERVICE_PID = "service.pid"; + public final static String SERVICE_ID = "service.id"; + public final static String SERVICE_RANKING = "service.ranking"; + + private String pid; + private Integer ranking = 0; + private Long id = 0l; + + public RankingKey(String pid, Integer ranking, Long id) { + super(); + this.pid = pid; + this.ranking = ranking; + this.id = id; + } + + public RankingKey(Map properties) { + this.pid = properties.containsKey(SERVICE_PID) ? properties.get(SERVICE_PID).toString() : null; + this.ranking = properties.containsKey(SERVICE_RANKING) + ? Integer.parseInt(properties.get(SERVICE_RANKING).toString()) + : 0; + this.id = properties.containsKey(SERVICE_ID) ? (Long) properties.get(SERVICE_ID) : null; + } + + @Override + public int hashCode() { + Integer result = 0; + if (pid != null) + result = +pid.hashCode(); + if (ranking != null) + result = +ranking; + return result; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return new RankingKey(pid, ranking, id); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(""); + if (pid != null) + sb.append(pid); + if (ranking != null && ranking != 0) + sb.append(' ').append(ranking); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RankingKey)) + return false; + RankingKey other = (RankingKey) obj; + return Objects.equals(pid, other.pid) && Objects.equals(ranking, other.ranking) && Objects.equals(id, other.id); + } + + @Override + public int compareTo(RankingKey o) { + if (pid != null && o.pid != null) { + if (pid.equals(o.pid)) { + if (ranking.equals(o.ranking)) + if (id != null && o.id != null) + return id.compareTo(o.id); + else + return 0; + else + return ranking.compareTo(o.ranking); + } else { + return pid.compareTo(o.pid); + } + + } else { + } + return -1; + } + + public String getPid() { + return pid; + } + + public Integer getRanking() { + return ranking; + } + + public Long getId() { + return id; + } + + public static RankingKey minPid(String pid) { + return new RankingKey(pid, Integer.MIN_VALUE, null); + } + + public static RankingKey maxPid(String pid) { + return new RankingKey(pid, Integer.MAX_VALUE, null); + } +} diff --git a/org.argeo.api.register/src/org/argeo/api/register/SimpleRegister.java b/org.argeo.api.register/src/org/argeo/api/register/SimpleRegister.java new file mode 100644 index 000000000..9ed7e765f --- /dev/null +++ b/org.argeo.api.register/src/org/argeo/api/register/SimpleRegister.java @@ -0,0 +1,124 @@ +package org.argeo.api.register; + +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; + +/** A minimal component register. */ +public class SimpleRegister implements ComponentRegister { + private final AtomicBoolean started = new AtomicBoolean(false); + private final IdentityHashMap> components = new IdentityHashMap<>(); + private final AtomicLong nextServiceId = new AtomicLong(0l); + + @Override + public long register(Component component) { + return registerComponent(component); + } + + @SuppressWarnings({ "unchecked" }) + @Override + public synchronized SortedSet> find(Class clss, + Predicate> filter) { + SortedSet> result = new TreeSet<>(); + instances: for (Object instance : components.keySet()) { + if (!clss.isAssignableFrom(instance.getClass())) + continue instances; + Component component = (Component) components.get(instance); + + if (component.isPublishedType(clss)) { + if (filter != null) { + filter.test(component.getProperties()); + } + result.add(component); + } + } + if (result.isEmpty()) + return null; + return result; + + } + + synchronized long registerComponent(Component component) { + if (started.get()) // TODO make it really dynamic + throw new IllegalStateException("Already activated"); + if (components.containsKey(component.get())) + throw new IllegalArgumentException("Already registered as component"); + components.put(component.get(), component); + return nextServiceId.incrementAndGet(); + } + + @Override + public synchronized Component get(Object instance) { + if (!components.containsKey(instance)) + throw new IllegalArgumentException("Not registered as component"); + return components.get(instance); + } + + @Override + public synchronized void activate() { + if (started.get()) + throw new IllegalStateException("Already activated"); + Set> constraints = new HashSet<>(); + for (Component component : components.values()) { + component.startActivating(); + constraints.add(component.getActivated()); + } + + // wait + try { + CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true)) + .get(); + } catch (InterruptedException e) { + throw new RuntimeException("Register activation has been interrupted", e); + } catch (ExecutionException e) { + if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) { + throw (RuntimeException) e.getCause(); + } else { + throw new IllegalStateException("Cannot activate register", e.getCause()); + } + } + } + + @Override + public synchronized void deactivate() { + if (!started.get()) + throw new IllegalStateException("Not activated"); + Set> constraints = new HashSet<>(); + for (Component component : components.values()) { + component.startDeactivating(); + constraints.add(component.getDeactivated()); + } + + // wait + try { + CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false)) + .get(); + } catch (InterruptedException e) { + throw new RuntimeException("Register deactivation has been interrupted", e); + } catch (ExecutionException e) { + if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) { + throw (RuntimeException) e.getCause(); + } else { + throw new IllegalStateException("Cannot deactivate register", e.getCause()); + } + } + } + + @Override + public synchronized boolean isActive() { + return started.get(); + } + + @Override + public synchronized void clear() { + components.clear(); + } +} diff --git a/org.argeo.api.uuid/.classpath b/org.argeo.api.uuid/.classpath index e801ebfb4..81fe078c2 100644 --- a/org.argeo.api.uuid/.classpath +++ b/org.argeo.api.uuid/.classpath @@ -1,6 +1,6 @@ - + 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 index 52becc85a..fc1c931ff 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java @@ -63,7 +63,7 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple } /** - * If positive, only clock_hi is taken from the argument (range & 0x3F00), if + * If positive, only clock_hi is taken from the argument (range amp; 0x3F00), if * negative, the full range of possible values is used. */ public void setCurrentClockSequenceRange(long range) { 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 index 1bbc4391f..4f2cf3765 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java @@ -12,7 +12,7 @@ import java.util.UUID; /** * Implementation of the basic RFC4122 algorithms. * - * @see https://datatracker.ietf.org/doc/html/rfc4122 + * @see "https://datatracker.ietf.org/doc/html/rfc4122" */ public abstract class AbstractUuidFactory implements UuidFactory { @@ -127,7 +127,7 @@ public abstract class AbstractUuidFactory implements UuidFactory { /** * Force this node id to be identified as no MAC address. * - * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5 + * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5" */ protected static void forceToNoMacAddress(byte[] nodeId, int offset) { assert nodeId != null && offset < nodeId.length; 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 index b883e0dcb..c9f130df9 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java @@ -13,7 +13,7 @@ public class BasicNameUuid extends TypedUuid { } /** - * Always returns true since it is unknown from which values it was + * Always returns true since it is unknown from which values it was * constructed.. */ @Override 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 index 5bab35980..fd158fb83 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java @@ -1,9 +1,5 @@ package org.argeo.api.uuid; -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.WARNING; - -import java.lang.System.Logger; import java.security.SecureRandom; import java.time.Clock; import java.time.Duration; @@ -13,6 +9,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicLong; import org.argeo.api.uuid.UuidFactory.TimeUuidState; @@ -25,11 +22,11 @@ import org.argeo.api.uuid.UuidFactory.TimeUuidState; * sequences. If that limit is reached, the clock sequence which has not be used * for the most time is reallocated to the new thread. It is assumed that the * context where time uUIDs will be generated will often be using thread pools - * (e.g. {@link ForkJoinPool#commonPool(), http server, database access, etc.) + * (e.g. {@link ForkJoinPool#commonPool()}, http server, database access, etc.) * and that such reallocation won't have to happen too often. */ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState { - private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName()); +// private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName()); /** The maximum possible value of the clocksequence. */ private final static int MAX_CLOCKSEQUENCE = 0x3F00; @@ -146,9 +143,7 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState { @Override public long getMostSignificantBits() { long timestamp = useTimestamp(); - long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low - | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid - | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version + long mostSig = TimeUuid.toMostSignificantBits(timestamp); return mostSig; } @@ -275,11 +270,11 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState { } assert holderToRemove != null; - long oldClockSequence = holderToRemove.clockSequence; +// long oldClockSequence = holderToRemove.clockSequence; holderToRemove.clockSequence = -1; activeHolders.remove(holderToRemove); - if (logger.isLoggable(WARNING)) - logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence); +// if (logger.isLoggable(WARNING)) +// logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence); } long newClockSequence = -1; @@ -300,13 +295,13 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState { // TODO use an iterator to check the values holder.setClockSequence(newClockSequence); activeHolders.put(holder, newClockSequence); - if (logger.isLoggable(DEBUG)) { - String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF) - : Long.toHexString(newClockSequence | 0x8000); - String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000); - logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId() - + " (in range " + rangeDesc + ")"); - } +// if (logger.isLoggable(DEBUG)) { +// String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF) +// : Long.toHexString(newClockSequence | 0x8000); +// String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000); +// logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId() +// + " (in range " + rangeDesc + ")"); +// } } private synchronized int getRangeSize() { 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 index 264e04706..130a90a84 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java @@ -1,9 +1,5 @@ package org.argeo.api.uuid; -import static java.lang.System.Logger.Level.DEBUG; -import static java.lang.System.Logger.Level.WARNING; - -import java.lang.System.Logger; import java.security.DrbgParameters; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -14,15 +10,20 @@ import java.util.UUID; * A configurable implementation of an {@link AsyncUuidFactory}, which can be * used as a base class for more optimised implementations. * - * @see https://datatracker.ietf.org/doc/html/rfc4122 + * @see "https://datatracker.ietf.org/doc/html/rfc4122" */ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory { - private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName()); +// private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName()); public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId) { this(initialClockRange, nodeId, 0); } + /** With a random node id. */ + public ConcurrentUuidFactory(long initialClockRange) { + this(initialClockRange, NodeIdSupplier.randomNodeId()); + } + public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId, int offset) { Objects.requireNonNull(nodeId); if (offset + 6 > nodeId.length) @@ -67,12 +68,14 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements T DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes())); } catch (NoSuchAlgorithmException e) { try { - logger.log(DEBUG, "DRBG secure random not found, using strong"); +// logger.log(DEBUG, "DRBG secure random not found, using strong"); secureRandom = SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException e1) { - logger.log(WARNING, "No strong secure random was found, using default"); +// logger.log(WARNING, "No strong secure random was found, using default"); secureRandom = new SecureRandom(); } + } catch (java.lang.NoClassDefFoundError e) {// Android + secureRandom = new SecureRandom(); } return secureRandom; } 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 index a9a5af17d..0775cbf75 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java @@ -5,7 +5,7 @@ import java.util.UUID; /** * A variant 6 {@link UUID}. * - * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1 + * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1" */ public class GUID extends TypedUuid { private static final long serialVersionUID = APM.SERIAL; @@ -26,7 +26,7 @@ public class GUID extends TypedUuid { *
  • P: (1db31359-bdd8-5a0f-b672-30c247d582c5)
  • * * - * @see https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring + * @see "https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring" */ public static String toString(UUID uuid, char format, boolean upperCase) { String str; 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 index fa68df1db..31fe37831 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java @@ -4,45 +4,66 @@ import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; +import java.util.Enumeration; import java.util.UUID; /** * An {@link UUID} factory whose node id (for time based UUIDs) is the hardware * MAC address as specified in RFC4122. * - * @see https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6 + * @see "https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6" */ public class MacAddressUuidFactory extends ConcurrentUuidFactory { - public final static UuidFactory DEFAULT = new MacAddressUuidFactory(); - public MacAddressUuidFactory() { - this(0); + this(0, localHardwareAddressAsNodeId()); } public MacAddressUuidFactory(long initialClockRange) { - super(initialClockRange, localHardwareAddressAsNodeId()); + this(initialClockRange, localHardwareAddressAsNodeId()); + } + + public MacAddressUuidFactory(byte[] hardwareAddress) { + this(0, hardwareAddress); } - public static byte[] localHardwareAddressAsNodeId() { + public MacAddressUuidFactory(long initialClockRange, byte[] hardwareAddress) { + super(initialClockRange, hardwareAddress); + } + + private static byte[] localHardwareAddressAsNodeId() { InetAddress localHost; try { localHost = InetAddress.getLocalHost(); NetworkInterface nic = NetworkInterface.getByInetAddress(localHost); - return hardwareAddressToNodeId(nic); + if (nic != null) + return hardwareAddressToNodeId(nic); + Enumeration netInterfaces = null; + try { + netInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw new IllegalStateException(e); + } + if (netInterfaces == null || !netInterfaces.hasMoreElements()) + throw new IllegalStateException("No interfaces"); + return hardwareAddressToNodeId(netInterfaces.nextElement()); } catch (UnknownHostException | SocketException e) { throw new IllegalStateException(e); } } - public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws SocketException { - byte[] hardwareAddress = nic.getHardwareAddress(); - final int length = 6; - byte[] arr = new byte[length]; - for (int i = 0; i < length; i++) { - arr[i] = hardwareAddress[length - 1 - i]; + public static byte[] hardwareAddressToNodeId(NetworkInterface nic) { + try { + byte[] hardwareAddress = nic.getHardwareAddress(); + final int length = 6; + byte[] arr = new byte[length]; + for (int i = 0; i < length; i++) { + arr[i] = hardwareAddress[length - 1 - i]; + } + return arr; + } catch (SocketException e) { + throw new IllegalStateException("Cannot retrieve hardware address from NIC", e); } - return arr; } } 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 index 94ec50da4..81d368d2c 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java @@ -1,13 +1,13 @@ package org.argeo.api.uuid; +import java.security.SecureRandom; import java.util.function.Supplier; /** A factory for node id base */ public interface NodeIdSupplier extends Supplier { static long toNodeIdBase(byte[] node) { assert node.length == 6; - return UuidFactory.LEAST_SIG_RFC4122_VARIANT - | (node[0] & 0xFFL) // + return UuidFactory.LEAST_SIG_RFC4122_VARIANT | (node[0] & 0xFFL) // | ((node[1] & 0xFFL) << 8) // | ((node[2] & 0xFFL) << 16) // | ((node[3] & 0xFFL) << 24) // @@ -19,4 +19,10 @@ public interface NodeIdSupplier extends Supplier { return (nodeId[0] & 1) != 0; } + static byte[] randomNodeId() { + SecureRandom random = new SecureRandom(); + byte[] nodeId = new byte[6]; + random.nextBytes(nodeId); + return nodeId; + } } 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 index b65772e9b..79b2b7032 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java @@ -14,8 +14,7 @@ public final class RandomUuid extends TypedUuid { } /** - * Always returns true since random UUIDs are by definition not - * opaque. + * Always returns true since random UUIDs are by definition opaque. */ @Override public final boolean isOpaque() { 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 index 2276cd268..2e0587d12 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java @@ -76,4 +76,23 @@ public class TimeUuid extends TypedUuid { Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, instant); return durationToTimestamp(duration); } + + /** + * Crate a time UUID with this instant as timestamp and clock and node id set to + * zero. + */ + public static UUID fromInstant(Instant instant) { + long timestamp = instantToTimestamp(instant); + long mostSig = toMostSignificantBits(timestamp); + UUID uuid = new UUID(mostSig, UuidFactory.LEAST_SIG_RFC4122_VARIANT); + return uuid; + } + + /** Convert timestamp in UUID format to most significant bits of a time UUID. */ + static long toMostSignificantBits(long timestamp) { + long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low + | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid + | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version + return mostSig; + } } 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 index 4ab6c2e4b..ee5aa6994 100644 --- a/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java +++ b/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java @@ -14,7 +14,7 @@ import java.util.function.Supplier; * {@link Supplier#get()} method MUST be a v4 UUID (random). * * @see UUID - * @see https://datatracker.ietf.org/doc/html/rfc4122 + * @see "https://datatracker.ietf.org/doc/html/rfc4122" */ public interface UuidFactory extends Supplier { @@ -184,7 +184,7 @@ public interface UuidFactory extends Supplier { * Whether this UUID is time based but was not generated from an IEEE 802 * address, as per Section 4.5 of RFC4122. * - * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5 + * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5" */ static boolean isTimeBasedWithMacAddress(UUID uuid) { if (uuid.version() == 1) { @@ -202,7 +202,7 @@ public interface UuidFactory extends Supplier { * The state of a time based UUID generator, as described and discussed in * section 4.2.1 of RFC4122. * - * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1 + * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1" */ interface TimeUuidState { /** Current node id and clock sequence for this thread. */ diff --git a/org.argeo.cms.cli/.classpath b/org.argeo.cms.cli/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.cli/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.cli/.project b/org.argeo.cms.cli/.project new file mode 100644 index 000000000..1514c530c --- /dev/null +++ b/org.argeo.cms.cli/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.cli + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.cli/bnd.bnd b/org.argeo.cms.cli/bnd.bnd new file mode 100644 index 000000000..07d35f219 --- /dev/null +++ b/org.argeo.cms.cli/bnd.bnd @@ -0,0 +1,58 @@ +Main-Class: org.argeo.cms.cli.ArgeoCli + +Class-Path: \ +org.argeo.api.acr.2.3.jar \ +org.argeo.api.cli.2.3.jar \ +org.argeo.api.cms.2.3.jar \ +org.argeo.api.uuid.2.3.jar \ +org.argeo.cms.2.3.jar \ +org.argeo.cms.ee.2.3.jar \ +../osgi/equinox/org.argeo.cms/org.argeo.cms.lib.equinox.2.3.jar \ +org.argeo.cms.lib.jetty.2.3.jar \ +org.argeo.cms.lib.sshd.2.3.jar \ +org.argeo.cms.ux.2.3.jar \ +org.argeo.init.2.3.jar \ +org.argeo.util.2.3.jar \ +../org.argeo.tp/com.fasterxml.jackson.core.jackson-annotations.2.13.jar \ +../org.argeo.tp/com.fasterxml.jackson.core.jackson-core.2.13.jar \ +../org.argeo.tp/com.fasterxml.jackson.core.jackson-databind.2.13.jar \ +../org.argeo.tp/com.googlecode.javaewah.JavaEWAH.1.1.jar \ +../org.argeo.tp/de.thjom.java.systemd.2.1.jar \ +../org.argeo.tp/javax.servlet.4.0.jar \ +../org.argeo.tp/javax.websocket.1.1.jar \ +../org.argeo.tp/org.apache.batik.1.16.jar \ +../org.argeo.tp/org.apache.batik.constants.1.16.jar \ +../org.argeo.tp/org.apache.batik.css.1.16.jar \ +../org.argeo.tp/org.apache.batik.i18n.1.16.jar \ +../org.argeo.tp/org.apache.batik.util.1.16.jar \ +../org.argeo.tp/org.apache.commons.cli.1.5.jar \ +../org.argeo.tp/org.apache.commons.fileupload.1.4.jar \ +../org.argeo.tp/org.apache.commons.io.2.11.jar \ +../org.argeo.tp/org.apache.httpcomponents.httpclient.4.5.jar \ +../org.argeo.tp/org.apache.httpcomponents.httpcore.4.4.jar \ +../org.argeo.tp/org.apache.httpcomponents.httpmime.4.5.jar \ +../org.argeo.tp/org.apache.xalan.2.7.jar \ +../org.argeo.tp/org.apache.xerces.2.12.jar \ +../org.argeo.tp/org.apache.xmlgraphics.2.7.jar \ +../org.argeo.tp/org.apache.xml.resolver.1.2.jar \ +../org.argeo.tp/org.argeo.ext.slf4j.2.3.jar \ +../org.argeo.tp/org.eclipse.jgit.6.3.jar \ +../org.argeo.tp/org.freeedesktop.dbus.4.2.jar \ +../org.argeo.tp/org.slf4j.api.1.7.jar \ +../org.argeo.tp/org.slf4j.commons.logging.1.7.jar \ +../org.argeo.tp/org.w3c.css.sac.1.3.jar \ +../org.argeo.tp/org.w3c.dom.smil.1.0.jar \ +../org.argeo.tp/org.w3c.dom.svg.1.1.jar \ +../org.argeo.tp.crypto/bcmail.1.72.jar \ +../org.argeo.tp.crypto/bcpg.1.72.jar \ +../org.argeo.tp.crypto/bcpkix.1.72.jar \ +../org.argeo.tp.crypto/bcprov.1.72.jar \ +../org.argeo.tp.crypto/bcutil.1.72.jar \ +../org.argeo.tp.crypto/net.i2p.crypto.eddsa.0.3.jar \ +../org.argeo.tp.crypto/org.apache.sshd.2.9.jar \ +../org.argeo.tp.crypto/org.apache.sshd.cli.2.9.jar \ +../org.argeo.tp.crypto/org.apache.sshd.git.2.9.jar \ +../org.argeo.tp.crypto/org.apache.sshd.putty.2.9.jar \ +../org.argeo.tp.crypto/org.apache.sshd.scp.2.9.jar \ +../org.argeo.tp.crypto/org.apache.sshd.sftp.2.9.jar \ +../org.argeo.tp.crypto/org.apache.tomcat.jni.9.0.jar \ diff --git a/org.argeo.cms.cli/build.properties b/org.argeo.cms.cli/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.cms.cli/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java new file mode 100644 index 000000000..b55f9d6ad --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java @@ -0,0 +1,33 @@ +package org.argeo.cms.cli; + +import org.apache.commons.cli.Option; +import org.argeo.api.cli.CommandsCli; +import org.argeo.cms.cli.posix.PosixCommands; +import org.argeo.cms.ssh.cli.SshCli; + +/** Argeo command line tools. */ +public class ArgeoCli extends CommandsCli { + public ArgeoCli(String commandName) { + super(commandName); + // Common options + options.addOption(Option.builder("v").hasArg().argName("verbose").desc("verbosity").build()); + options.addOption( + Option.builder("D").hasArgs().argName("property=value").desc("use value for given property").build()); + + // common + addCommandsCli(new CmsCommands("cms")); + addCommandsCli(new SshCli("ssh")); + addCommandsCli(new PosixCommands("posix")); + addCommandsCli(new FsCommands("fs")); + } + + @Override + public String getDescription() { + return "Argeo CMS utilities"; + } + + public static void main(String[] args) { + mainImpl(new ArgeoCli("argeo"), args); + } + +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java new file mode 100644 index 000000000..516c7688f --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java @@ -0,0 +1,21 @@ +package org.argeo.cms.cli; + +import org.argeo.api.cli.CommandsCli; + +public class CmsCli extends CommandsCli { + + public CmsCli(String commandName) { + super(commandName); + addCommand("launch", new StaticCmsLaunch()); + } + + @Override + public String getDescription() { + return "Static CMS utilities."; + } + + public static void main(String[] args) { + mainImpl(new CmsCli("cms"), args); + } + +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java new file mode 100644 index 000000000..50977d1e1 --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java @@ -0,0 +1,128 @@ +package org.argeo.cms.cli; + +import java.net.URI; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.argeo.api.cli.CommandsCli; +import org.argeo.api.cli.DescribedCommand; +import org.argeo.cms.client.CmsClient; +import org.argeo.cms.client.WebSocketPing; + +/** Commands dealing with CMS. */ +public class CmsCommands extends CommandsCli { + final static Option connectOption = Option.builder().option("c").longOpt("connect").desc("server to connect to") + .hasArg(true).build(); + + public CmsCommands(String commandName) { + super(commandName); + addCommand("ping", new Ping()); + addCommand("get", new Get()); + addCommand("status", new Status()); + addCommand("event", new EventCommands("event")); + } + + @Override + public String getDescription() { + return "Utilities related to an Argeo CMS"; + } + + class Ping implements DescribedCommand { + @Override + public Options getOptions() { + Options options = new Options(); + options.addOption(connectOption); + return options; + } + + @Override + public Void apply(List t) { + CommandLine line = toCommandLine(t); + String uriArg = line.getOptionValue(connectOption); + // TODO make it more robust (trailing /, etc.) + URI uri = URI.create(uriArg); + if ("".equals(uri.getPath())) { + uri = URI.create(uri.toString() + "/cms/status/ping"); + } + new WebSocketPing(uri).run(); + return null; + } + + @Override + public String getUsage() { + return "[ws|wss]://host:port/"; + } + + @Override + public String getDescription() { + return "Test whether an Argeo CMS is available, without auhtentication"; + } + + } + + class Get implements DescribedCommand { + + @Override + public Options getOptions() { + Options options = new Options(); + options.addOption(connectOption); + return options; + } + + @Override + public String apply(List t) { + CommandLine line = toCommandLine(t); + List remaining = line.getArgList(); + String additionalUri = null; + if (remaining.size() != 0) { + additionalUri = remaining.get(0); + } + + String connectUri = line.getOptionValue(connectOption); + CmsClient cmsClient = new CmsClient(URI.create(connectUri)); + return additionalUri != null ? cmsClient.getAsString(URI.create(additionalUri)) : cmsClient.getAsString(); + } + + @Override + public String getUsage() { + return "[URI]"; + } + + @Override + public String getDescription() { + return "Retrieve this URI as a string"; + } + + } + + class Status implements DescribedCommand { + + @Override + public Options getOptions() { + Options options = new Options(); + options.addOption(connectOption); + return options; + } + + @Override + public String apply(List t) { + CommandLine line = toCommandLine(t); + String connectUri = line.getOptionValue(connectOption); + CmsClient cmsClient = new CmsClient(URI.create(connectUri)); + return cmsClient.getAsString(URI.create("/cms/status")); + } + + @Override + public String getUsage() { + return "[URI]"; + } + + @Override + public String getDescription() { + return "Retrieve the CMS status as a string"; + } + + } +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/EventCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/EventCommands.java new file mode 100644 index 000000000..009ad455b --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/EventCommands.java @@ -0,0 +1,64 @@ +package org.argeo.cms.cli; + +import java.net.URI; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.argeo.api.cli.CommandArgsException; +import org.argeo.api.cli.CommandsCli; +import org.argeo.api.cli.DescribedCommand; +import org.argeo.cms.client.WebSocketEventClient; + +/** Commands dealing with CMS events. */ +public class EventCommands extends CommandsCli { + public EventCommands(String commandName) { + super(commandName); + addCommand("listen", new EventListent()); + } + + @Override + public String getDescription() { + return "Utilities related to an Argeo CMS"; + } + + class EventListent implements DescribedCommand { + + @Override + public Options getOptions() { + Options options = new Options(); + options.addOption(CmsCommands.connectOption); + return options; + } + + @Override + public Void apply(List t) { + CommandLine line = toCommandLine(t); + List remaining = line.getArgList(); + if (remaining.size() == 0) { + throw new CommandArgsException("There must be at least one argument"); + } + String topic = remaining.get(0); + + String uriArg = line.getOptionValue(CmsCommands.connectOption); + // TODO make it more robust (trailing /, etc.) + URI uri = URI.create(uriArg); + if ("".equals(uri.getPath())) { + uri = URI.create(uri.toString() + "/cms/status/event/" + topic); + } + new WebSocketEventClient(uri).run(); + return null; + } + + @Override + public String getUsage() { + return "TOPIC"; + } + + @Override + public String getDescription() { + return "Listen to events on a topic"; + } + + } +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/FileSync.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/FileSync.java new file mode 100644 index 000000000..dc4c689d0 --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/FileSync.java @@ -0,0 +1,103 @@ +package org.argeo.cms.cli; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.argeo.api.cli.CommandArgsException; +import org.argeo.api.cli.DescribedCommand; +import org.argeo.cms.file.PathSync; +import org.argeo.cms.file.SyncResult; + +public class FileSync implements DescribedCommand> { + 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 apply(List t) { + try { + CommandLine line = toCommandLine(t); + List 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 remaining = line.getArgList(); +// if (remaining.size() == 0) { +// System.err.println("There must be at least one argument"); +// printHelp(options); +// System.exit(1); +// } +// URI sourceUri = new URI(remaining.get(0)); +// URI targetUri; +// if (remaining.size() == 1) { +// targetUri = Paths.get(System.getProperty("user.dir")).toUri(); +// } else { +// targetUri = new URI(remaining.get(1)); +// } +// PathSync pathSync = new PathSync(sourceUri, targetUri); +// pathSync.run(); +// } catch (Exception exp) { +// exp.printStackTrace(); +// printHelp(options); +// System.exit(1); +// } + } + +// public static void printHelp(Options options) { +// HelpFormatter formatter = new HelpFormatter(); +// formatter.printHelp("sync SRC [DEST]", options, true); +// } + + @Override + public String getDescription() { + return "Synchronises files"; + } + +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/FsCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/FsCommands.java new file mode 100644 index 000000000..46ec142d8 --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/FsCommands.java @@ -0,0 +1,18 @@ +package org.argeo.cms.cli; + +import org.argeo.api.cli.CommandsCli; + +/** File utilities. */ +public class FsCommands extends CommandsCli { + + public FsCommands(String commandName) { + super(commandName); + addCommand("sync", new FileSync()); + } + + @Override + public String getDescription() { + return "Utilities around files and file systems"; + } + +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java new file mode 100644 index 000000000..9b5359832 --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java @@ -0,0 +1,52 @@ +package org.argeo.cms.cli; + +import java.lang.management.ManagementFactory; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.argeo.api.cli.DescribedCommand; +import org.argeo.cms.runtime.StaticCms; + +public class StaticCmsLaunch implements DescribedCommand { + private Option dataOption; + + @Override + public Options getOptions() { + Options options = new Options(); + dataOption = Option.builder().longOpt("data").hasArg().required() + .desc("path to the writable data area (mandatory)").build(); + options.addOption(dataOption); + return options; + } + + @Override + public String apply(List args) { + CommandLine cl = toCommandLine(args); + String dataPath = cl.getOptionValue(dataOption); + + Path instancePath = Paths.get(dataPath); + System.setProperty("osgi.instance.area", instancePath.toUri().toString()); + System.setProperty("argeo.http.port", "0"); + + StaticCms staticCms = new StaticCms(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown")); + staticCms.start(); + + long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); + System.out.println("Static CMS available in " + jvmUptime + " ms."); + + staticCms.waitForStop(); + + return null; + } + + @Override + public String getDescription() { + return "Launch a static CMS"; + } + +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/Echo.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/Echo.java new file mode 100644 index 000000000..28e64466d --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/Echo.java @@ -0,0 +1,46 @@ +package org.argeo.cms.cli.posix; + +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.argeo.api.cli.DescribedCommand; + +public class Echo implements DescribedCommand { + + @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 args) { + CommandLine cl = toCommandLine(args); + + StringBuffer sb = new StringBuffer(); + for (String s : cl.getArgList()) { + sb.append(s).append(' '); + } + + if (cl.hasOption('n')) { + sb.deleteCharAt(sb.length() - 1); + } else { + sb.setCharAt(sb.length() - 1, '\n'); + } + return sb.toString(); + } + +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/PosixCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/PosixCommands.java new file mode 100644 index 000000000..97824299c --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/PosixCommands.java @@ -0,0 +1,21 @@ +package org.argeo.cms.cli.posix; + +import org.argeo.api.cli.CommandsCli; + +/** POSIX commands. */ +public class PosixCommands extends CommandsCli { + + public PosixCommands(String commandName) { + super(commandName); + addCommand("echo", new Echo()); + } + + @Override + public String getDescription() { + return "Reimplementation of some POSIX commands in plain Java"; + } + + public static void main(String[] args) { + mainImpl(new PosixCommands("argeo-posix"), args); + } +} diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/package-info.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/package-info.java new file mode 100644 index 000000000..8a6785c3f --- /dev/null +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/package-info.java @@ -0,0 +1,2 @@ +/** Posix CLI commands. */ +package org.argeo.cms.cli.posix; \ No newline at end of file diff --git a/org.argeo.cms.ee/.classpath b/org.argeo.cms.ee/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.ee/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.ee/.project b/org.argeo.cms.ee/.project new file mode 100644 index 000000000..4b68cdd92 --- /dev/null +++ b/org.argeo.cms.ee/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.ee + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml new file mode 100644 index 000000000..00fcaff99 --- /dev/null +++ b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml new file mode 100644 index 000000000..7540a2cdb --- /dev/null +++ b/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms.ee/OSGI-INF/statusHandler.xml b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml new file mode 100644 index 000000000..b6e9bfd05 --- /dev/null +++ b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms.ee/bnd.bnd b/org.argeo.cms.ee/bnd.bnd new file mode 100644 index 000000000..f09995c00 --- /dev/null +++ b/org.argeo.cms.ee/bnd.bnd @@ -0,0 +1,12 @@ +Import-Package:\ +org.osgi.service.http;version=0.0.0,\ +org.osgi.service.http.whiteboard;version=0.0.0,\ +org.osgi.framework.namespace;version=0.0.0,\ +org.argeo.cms.osgi,\ +javax.servlet.*;version="[3,5)",\ +* + +Service-Component:\ +OSGI-INF/pkgServletContext.xml,\ +OSGI-INF/pkgServlet.xml,\ +OSGI-INF/statusHandler.xml,\ diff --git a/org.argeo.cms.ee/build.properties b/org.argeo.cms.ee/build.properties new file mode 100644 index 000000000..eb170c950 --- /dev/null +++ b/org.argeo.cms.ee/build.properties @@ -0,0 +1,6 @@ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/jettyServiceFactory.xml,\ + OSGI-INF/statusHandler.xml +source.. = src/ +output.. = bin/ diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java new file mode 100644 index 000000000..672722946 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java @@ -0,0 +1,80 @@ +package org.argeo.cms.integration; + +import java.io.IOException; +import java.io.Writer; + +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.util.ExceptionsChain; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** Serialisable wrapper of a {@link Throwable}. */ +public class CmsExceptionsChain extends ExceptionsChain { + public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class); + + public CmsExceptionsChain() { + super(); + } + + public CmsExceptionsChain(Throwable exception) { + super(exception); + if (log.isDebugEnabled()) + log.error("Exception chain", exception); + } + + public String toJsonString(ObjectMapper objectMapper) { + try { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Cannot write system exceptions " + toString(), e); + } + } + + public void writeAsJson(ObjectMapper objectMapper, Writer writer) { + try { + JsonGenerator jg = objectMapper.writerWithDefaultPrettyPrinter().getFactory().createGenerator(writer); + jg.writeObject(this); + } catch (IOException e) { + throw new IllegalStateException("Cannot write system exceptions " + toString(), e); + } + } + + public void writeAsJson(ObjectMapper objectMapper, HttpServletResponse resp) { + try { + resp.setContentType("application/json"); + resp.setStatus(500); + writeAsJson(objectMapper, resp.getWriter()); + } catch (IOException e) { + throw new IllegalStateException("Cannot write system exceptions " + toString(), e); + } + } + +// public static void main(String[] args) throws Exception { +// try { +// try { +// try { +// testDeeper(); +// } catch (Exception e) { +// throw new Exception("Less deep exception", e); +// } +// } catch (Exception e) { +// throw new RuntimeException("Top exception", e); +// } +// } catch (Exception e) { +// CmsExceptionsChain systemErrors = new CmsExceptionsChain(e); +// ObjectMapper objectMapper = new ObjectMapper(); +// System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(systemErrors)); +// System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e)); +// e.printStackTrace(); +// } +// } +// +// static void testDeeper() throws Exception { +// throw new IllegalStateException("Deep exception"); +// } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java new file mode 100644 index 000000000..29a3137bb --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java @@ -0,0 +1,112 @@ +package org.argeo.cms.integration; + +import java.io.IOException; +import java.util.Locale; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsSessionId; +import org.argeo.cms.auth.RemoteAuthCallback; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; +import org.osgi.service.useradmin.Authorization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** Externally authenticate an http session. */ +public class CmsLoginServlet extends HttpServlet { + public final static String PARAM_USERNAME = "username"; + public final static String PARAM_PASSWORD = "password"; + + private static final long serialVersionUID = 2478080654328751539L; + private ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + LoginContext lc = null; + String username = req.getParameter(PARAM_USERNAME); + String password = req.getParameter(PARAM_PASSWORD); + ServletHttpRequest request = new ServletHttpRequest(req); + ServletHttpResponse response = new ServletHttpResponse(resp); + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback && username != null) + ((NameCallback) callback).setName(username); + else if (callback instanceof PasswordCallback && password != null) + ((PasswordCallback) callback).setPassword(password.toCharArray()); + else if (callback instanceof RemoteAuthCallback) { + ((RemoteAuthCallback) callback).setRequest(request); + ((RemoteAuthCallback) callback).setResponse(response); + } + } + } + }); + lc.login(); + + Subject subject = lc.getSubject(); + CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class)); + if (cmsSessionId == null) { + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class)); + Locale locale = extractFrom(subject.getPublicCredentials(Locale.class)); + + CmsSessionDescriptor cmsSessionDescriptor = new CmsSessionDescriptor(authorization.getName(), + cmsSessionId.getUuid().toString(), authorization.getRoles(), authorization.toString(), + locale != null ? locale.toString() : null); + + resp.setContentType("application/json"); + JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter()); + jg.writeObject(cmsSessionDescriptor); + + String redirectTo = redirectTo(req); + if (redirectTo != null) + resp.sendRedirect(redirectTo); + } catch (LoginException e) { + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + } + + protected T extractFrom(Set creds) { + if (creds.size() > 0) + return creds.iterator().next(); + else + return null; + } + + /** + * To be overridden in order to return a richer {@link CmsSessionDescriptor} to + * be serialized. + */ + protected CmsSessionDescriptor enrichJson(CmsSessionDescriptor cmsSessionDescriptor) { + return cmsSessionDescriptor; + } + + protected String redirectTo(HttpServletRequest request) { + return null; + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java new file mode 100644 index 000000000..d18637d3f --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java @@ -0,0 +1,79 @@ +package org.argeo.cms.integration; + +import java.io.IOException; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsSessionId; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.auth.RemoteAuthCallback; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; + +/** Externally authenticate an http session. */ +public class CmsLogoutServlet extends HttpServlet { + private static final long serialVersionUID = 2478080654328751539L; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + ServletHttpRequest httpRequest = new ServletHttpRequest(request); + ServletHttpResponse httpResponse = new ServletHttpResponse(response); + LoginContext lc = null; + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, + new RemoteAuthCallbackHandler(httpRequest, httpResponse) { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof RemoteAuthCallback) { + ((RemoteAuthCallback) callback).setRequest(httpRequest); + ((RemoteAuthCallback) callback).setResponse(httpResponse); + } + } + } + }); + lc.login(); + + Subject subject = lc.getSubject(); + CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class)); + if (cmsSessionId != null) {// logged in + CurrentUser.logoutCmsSession(subject); + } + + } catch (LoginException e) { + // ignore + } + + String redirectTo = redirectTo(request); + if (redirectTo != null) + response.sendRedirect(redirectTo); + } + + protected T extractFrom(Set creds) { + if (creds.size() > 0) + return creds.iterator().next(); + else + return null; + } + + protected String redirectTo(HttpServletRequest request) { + return null; + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java new file mode 100644 index 000000000..09f17ae02 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java @@ -0,0 +1,80 @@ +package org.argeo.cms.integration; + +import java.io.IOException; +import java.security.AccessControlContext; +import java.util.Map; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; +import org.osgi.service.http.context.ServletContextHelper; + +/** Manages security access to servlets. */ +public class CmsPrivateServletContext extends ServletContextHelper { + public final static String LOGIN_PAGE = "argeo.cms.integration.loginPage"; + public final static String LOGIN_SERVLET = "argeo.cms.integration.loginServlet"; + private String loginPage; + private String loginServlet; + + public void init(Map properties) { + loginPage = properties.get(LOGIN_PAGE); + loginServlet = properties.get(LOGIN_SERVLET); + } + + /** + * Add the {@link AccessControlContext} as a request attribute, or redirect to + * the login page. + */ + @Override + public boolean handleSecurity(final HttpServletRequest req, HttpServletResponse resp) throws IOException { + LoginContext lc = null; + ServletHttpRequest request = new ServletHttpRequest(req); + ServletHttpResponse response = new ServletHttpResponse(resp); + + String pathInfo = req.getPathInfo(); + String servletPath = req.getServletPath(); + if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet)) + return true; + try { + lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(request, response)); + lc.login(); + } catch (LoginException e) { + lc = processUnauthorized(req, resp); + if (lc == null) + return false; + } +// Subject.doAs(lc.getSubject(), new PrivilegedAction() { +// +// @Override +// public Void run() { +// // TODO also set login context in order to log out ? +// RemoteAuthUtils.configureRequestSecurity(request); +// return null; +// } +// +// }); + + return true; + } + +// @Override +// public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) { +// RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req)); +// } + + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + try { + response.sendRedirect(loginPage); + } catch (IOException e) { + throw new RuntimeException("Cannot redirect to login page", e); + } + return null; + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java new file mode 100644 index 000000000..30de616a2 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java @@ -0,0 +1,96 @@ +package org.argeo.cms.integration; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + +import org.argeo.api.cms.CmsSession; +import org.osgi.service.useradmin.Authorization; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** A serializable descriptor of an internal {@link CmsSession}. */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CmsSessionDescriptor implements Serializable, Authorization { + private static final long serialVersionUID = 8592162323372641462L; + + private String name; + private String cmsSessionId; + private String displayName; + private String locale; + private Set roles; + + public CmsSessionDescriptor() { + } + + public CmsSessionDescriptor(String name, String cmsSessionId, String[] roles, String displayName, String locale) { + this.name = name; + this.displayName = displayName; + this.cmsSessionId = cmsSessionId; + this.locale = locale; + this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getCmsSessionId() { + return cmsSessionId; + } + + public void setCmsSessionId(String cmsSessionId) { + this.cmsSessionId = cmsSessionId; + } + + public Boolean isAnonymous() { + return name == null; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + @Override + public boolean hasRole(String name) { + return roles.contains(name); + } + + @Override + public String[] getRoles() { + return roles.toArray(new String[roles.size()]); + } + + public void setRoles(String[] roles) { + this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))); + } + + @Override + public int hashCode() { + return cmsSessionId != null ? cmsSessionId.hashCode() : super.hashCode(); + } + + @Override + public String toString() { + return displayName != null ? displayName : name != null ? name : super.toString(); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java new file mode 100644 index 000000000..c355ecd8d --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java @@ -0,0 +1,117 @@ +package org.argeo.cms.integration; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.Set; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.acr.ldap.NamingUtils; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.cms.auth.RemoteAuthCallback; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; +import org.osgi.service.useradmin.Authorization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** Provides access to tokens. */ +public class CmsTokenServlet extends HttpServlet { + private static final long serialVersionUID = 302918711430864140L; + + public final static String PARAM_EXPIRY_DATE = "expiryDate"; + public final static String PARAM_TOKEN = "token"; + + private final static int DEFAULT_HOURS = 24; + + private CmsUserManager userManager; + private ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + ServletHttpRequest request = new ServletHttpRequest(req); + ServletHttpResponse response = new ServletHttpResponse(resp); + LoginContext lc = null; + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof RemoteAuthCallback) { + ((RemoteAuthCallback) callback).setRequest(request); + ((RemoteAuthCallback) callback).setResponse(response); + } + } + } + }); + lc.login(); + } catch (LoginException e) { + // ignore + } + + try { + Subject subject = lc.getSubject(); + Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class)); + String token = UUID.randomUUID().toString(); + String expiryDateStr = req.getParameter(PARAM_EXPIRY_DATE); + ZonedDateTime expiryDate; + if (expiryDateStr != null) { + expiryDate = NamingUtils.ldapDateToZonedDateTime(expiryDateStr); + } else { + expiryDate = ZonedDateTime.now().plusHours(DEFAULT_HOURS); + expiryDateStr = NamingUtils.instantToLdapDate(expiryDate); + } + userManager.addAuthToken(authorization.getName(), token, expiryDate); + + TokenDescriptor tokenDescriptor = new TokenDescriptor(); + tokenDescriptor.setUsername(authorization.getName()); + tokenDescriptor.setToken(token); + tokenDescriptor.setExpiryDate(expiryDateStr); +// tokenDescriptor.setRoles(Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)))); + + resp.setContentType("application/json"); + JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter()); + jg.writeObject(tokenDescriptor); + } catch (Exception e) { + new CmsExceptionsChain(e).writeAsJson(objectMapper, resp); + } + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // temporarily wrap POST for ease of testing + doPost(req, resp); + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + String token = req.getParameter(PARAM_TOKEN); + userManager.expireAuthToken(token); + } catch (Exception e) { + new CmsExceptionsChain(e).writeAsJson(objectMapper, resp); + } + } + + protected T extractFrom(Set creds) { + if (creds.size() > 0) + return creds.iterator().next(); + else + return null; + } + + public void setUserManager(CmsUserManager userManager) { + this.userManager = userManager; + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java new file mode 100644 index 000000000..1541b4f29 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java @@ -0,0 +1,49 @@ +package org.argeo.cms.integration; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** A serializable descriptor of a token. */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TokenDescriptor implements Serializable { + private static final long serialVersionUID = -6607393871416803324L; + + private String token; + private String username; + private String expiryDate; +// private Set roles; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + +// public Set getRoles() { +// return roles; +// } +// +// public void setRoles(Set roles) { +// this.roles = roles; +// } + + public String getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(String expiryDate) { + this.expiryDate = expiryDate; + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java new file mode 100644 index 000000000..1405737ee --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS integration (JSON, web services). */ +package org.argeo.cms.integration; \ No newline at end of file diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java new file mode 100644 index 000000000..d3c0eb540 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java @@ -0,0 +1,120 @@ +package org.argeo.cms.servlet; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.servlet.internal.HttpUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.http.context.ServletContextHelper; + +/** + * Default servlet context degrading to anonymous if the the session is not + * pre-authenticated. + */ +public class CmsServletContext extends ServletContextHelper { + private final static CmsLog log = CmsLog.getLog(CmsServletContext.class); + // use CMS bundle for resources + private Bundle bundle = FrameworkUtil.getBundle(getClass()); + + private final String httpAuthRealm = "Argeo"; + private final boolean forceBasic = false; + + public void init(Map properties) { + + } + + public void destroy() { + + } + + @Override + public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (log.isTraceEnabled()) + HttpUtils.logRequestHeaders(log, request); + RemoteAuthRequest remoteAuthRequest = new ServletHttpRequest(request); + RemoteAuthResponse remoteAuthResponse = new ServletHttpResponse(response); + ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader()); + LoginContext lc; + try { + lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse)); + lc.login(); + } catch (LoginException e) { + if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) { + int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest, + remoteAuthResponse, httpAuthRealm, + forceBasic); + response.setStatus(statusCode); + return false; + + } else { + lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse); + } + if (lc == null) + return false; + } finally { + Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader); + } + +// Subject subject = lc.getSubject(); +// Subject.doAs(subject, new PrivilegedAction() { +// +// @Override +// public Void run() { +// // TODO also set login context in order to log out ? +// RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest); +// return null; +// } +// +// }); + return true; + } + +// @Override +// public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { +// RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request)); +// } + + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { + return false; + } + +// protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { +// // anonymous +// ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); +// try { +// Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader()); +// LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext( +// new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); +// lc.login(); +// return lc; +// } catch (LoginException e1) { +// if (log.isDebugEnabled()) +// log.error("Cannot log in as anonymous", e1); +// return null; +// } finally { +// Thread.currentThread().setContextClassLoader(currentContextClassLoader); +// } +// } + + @Override + public URL getResource(String name) { + // TODO make it more robust and versatile + // if used directly it can only load from within this bundle + return bundle.getResource(name); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java new file mode 100644 index 000000000..cd28b6e75 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java @@ -0,0 +1,42 @@ +package org.argeo.cms.servlet; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; + +/** Servlet context forcing authentication. */ +public class PrivateWwwAuthServletContext extends CmsServletContext { + // TODO make it configurable +// private final String httpAuthRealm = "Argeo"; +// private final boolean forceBasic = false; + + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, + RemoteAuthResponse remoteAuthResponse) { + return true; + } + + +// @Override +// protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { +// askForWwwAuth(request, response); +// return null; +// } +// +// protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { +// // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic +// // realm=\"" + httpAuthRealm + "\""); +// if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO +// response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE); +// else +// response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), +// HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + httpAuthRealm + "\""); +// +// // response.setDateHeader("Date", System.currentTimeMillis()); +// // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * +// // 60 * 60 * 1000)); +// // response.setHeader("Accept-Ranges", "bytes"); +// // response.setHeader("Connection", "Keep-Alive"); +// // response.setHeader("Keep-Alive", "timeout=5, max=97"); +// // response.setContentType("text/html; charset=UTF-8"); +// response.setStatus(401); +// } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java new file mode 100644 index 000000000..54c880435 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java @@ -0,0 +1,67 @@ +package org.argeo.cms.servlet; + +import java.util.Locale; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthSession; + +public class ServletHttpRequest implements RemoteAuthRequest { + private final HttpServletRequest request; + + public ServletHttpRequest(HttpServletRequest request) { + Objects.requireNonNull(request); + this.request = request; + } + + @Override + public RemoteAuthSession getSession() { + HttpSession httpSession = request.getSession(false); + if (httpSession == null) + return null; + return new ServletHttpSession(httpSession); + } + + @Override + public RemoteAuthSession createSession() { + return new ServletHttpSession(request.getSession(true)); + } + + @Override + public Locale getLocale() { + return request.getLocale(); + } + + @Override + public Object getAttribute(String key) { + return request.getAttribute(key); + } + + @Override + public void setAttribute(String key, Object object) { + request.setAttribute(key, object); + } + + @Override + public String getHeader(String key) { + return request.getHeader(key); + } + + @Override + public String getRemoteAddr() { + return request.getRemoteAddr(); + } + + @Override + public int getLocalPort() { + return request.getLocalPort(); + } + + @Override + public int getRemotePort() { + return request.getRemotePort(); + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java new file mode 100644 index 000000000..0c600e54b --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java @@ -0,0 +1,27 @@ +package org.argeo.cms.servlet; + +import java.util.Objects; + +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.RemoteAuthResponse; + +public class ServletHttpResponse implements RemoteAuthResponse { + private final HttpServletResponse response; + + public ServletHttpResponse(HttpServletResponse response) { + Objects.requireNonNull(response); + this.response = response; + } + + @Override + public void setHeader(String headerName, String value) { + response.setHeader(headerName, value); + } + + @Override + public void addHeader(String headerName, String value) { + response.addHeader(headerName, value); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java new file mode 100644 index 000000000..8d087daa7 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java @@ -0,0 +1,28 @@ +package org.argeo.cms.servlet; + +import org.argeo.cms.auth.RemoteAuthSession; + +public class ServletHttpSession implements RemoteAuthSession { + private javax.servlet.http.HttpSession session; + + public ServletHttpSession(javax.servlet.http.HttpSession session) { + super(); + this.session = session; + } + + @Override + public boolean isValid() { + try {// test http session + session.getCreationTime(); + return true; + } catch (IllegalStateException ise) { + return false; + } + } + + @Override + public String getId() { + return session.getId(); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletUtils.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletUtils.java new file mode 100644 index 000000000..072417a8a --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletUtils.java @@ -0,0 +1,52 @@ +package org.argeo.cms.servlet; + +import static org.argeo.cms.http.HttpHeader.VIA; +import static org.argeo.cms.http.HttpHeader.X_FORWARDED_HOST; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +/** Servlet utilities. */ +public class ServletUtils { + + /** + * The base URL for this query (without any path component (not even an ending + * '/'), taking into account reverse proxies. + */ + public static StringBuilder getRequestUrlBase(HttpServletRequest req) { + List viaHosts = new ArrayList<>(); + for (Enumeration it = req.getHeaders(VIA.getHeaderName()); it.hasMoreElements();) { + String[] arr = it.nextElement().split(" "); + viaHosts.add(arr[1]); + } + + String outerHost = viaHosts.isEmpty() ? null : viaHosts.get(0); + if (outerHost == null) { + // Try non-standard header + String forwardedHost = req.getHeader(X_FORWARDED_HOST.getHeaderName()); + if (forwardedHost != null) { + String[] arr = forwardedHost.split(","); + outerHost = arr[0]; + } + } + + URI requestUrl = URI.create(req.getRequestURL().toString()); + + boolean isReverseProxy = outerHost != null && !outerHost.equals(requestUrl.getHost()); + if (isReverseProxy) { + String protocol = req.isSecure() ? "https" : "http"; + return new StringBuilder(protocol + "://" + outerHost); + } else { + return new StringBuilder(requestUrl.getScheme() + "://" + requestUrl.getHost() + + (requestUrl.getPort() > 0 ? ":" + requestUrl.getPort() : "")); + } + } + + /** singleton */ + private ServletUtils() { + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java new file mode 100644 index 000000000..63d59a88d --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java @@ -0,0 +1,62 @@ +package org.argeo.cms.servlet.httpserver; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.RemoteAuthSession; +import org.argeo.cms.servlet.ServletHttpSession; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpPrincipal; + +/** + * An {@link HttpServlet} which integrates an {@link HttpContext} and its + * {@link Authenticator} in a servlet container. + */ +public class HttpContextServlet extends HttpServlet { + private static final long serialVersionUID = 2321612280413662738L; + + private final HttpContext httpContext; + + public HttpContextServlet(HttpContext httpContext) { + this.httpContext = httpContext; + } + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try (ServletHttpExchange httpExchange = new ServletHttpExchange(httpContext, req, resp)) { + ServletHttpSession httpSession = new ServletHttpSession(req.getSession()); + httpExchange.setAttribute(RemoteAuthSession.class.getName(), httpSession); + Authenticator authenticator = httpContext.getAuthenticator(); + if (authenticator != null) { + Authenticator.Result authenticationResult = authenticator.authenticate(httpExchange); + if (authenticationResult instanceof Authenticator.Success) { + HttpPrincipal httpPrincipal = ((Authenticator.Success) authenticationResult).getPrincipal(); + httpExchange.setPrincipal(httpPrincipal); + } else if (authenticationResult instanceof Authenticator.Retry) { + httpExchange.sendResponseHeaders((((Authenticator.Retry) authenticationResult).getResponseCode()), + -1); + resp.flushBuffer(); + return; + } else if (authenticationResult instanceof Authenticator.Failure) { + httpExchange.sendResponseHeaders(((Authenticator.Failure) authenticationResult).getResponseCode(), + -1); + resp.flushBuffer(); + return; + } else { + throw new UnsupportedOperationException( + "Authentication result " + authenticationResult.getClass().getName() + " is not supported"); + } + } + + HttpHandler httpHandler = httpContext.getHandler(); + httpHandler.handle(httpExchange); + } + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java new file mode 100644 index 000000000..f5e9c0394 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java @@ -0,0 +1,188 @@ +package org.argeo.cms.servlet.httpserver; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.net.ssl.SSLSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.net.httpserver.HttpsExchange; + +/** Integrates {@link HttpsExchange} in a servlet container. */ +class ServletHttpExchange extends HttpsExchange { + private final HttpContext httpContext; + private final HttpServletRequest httpServletRequest; + private final HttpServletResponse httpServletResponse; + + private final Headers requestHeaders; + private final Headers responseHeaders; + + private InputStream filteredIn; + private OutputStream filteredOut; + + private HttpPrincipal principal; + + public ServletHttpExchange(HttpContext httpContext, HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse) { + this.httpContext = httpContext; + this.httpServletRequest = httpServletRequest; + this.httpServletResponse = httpServletResponse; + + // request headers + requestHeaders = new Headers(); + for (Enumeration headerNames = httpServletRequest.getHeaderNames(); headerNames.hasMoreElements();) { + String headerName = headerNames.nextElement(); + List values = new ArrayList<>(); + for (Enumeration headerValues = httpServletRequest.getHeaders(headerName); headerValues + .hasMoreElements();) + values.add(headerValues.nextElement()); + requestHeaders.put(headerName, values); + } + + responseHeaders = new Headers(); + } + + @Override + public SSLSession getSSLSession() { + Object obj = httpServletRequest.getAttribute("javax.net.ssl.session"); + if (obj == null || !(obj instanceof SSLSession)) + throw new IllegalStateException("SSL session not found"); + return (SSLSession) obj; + } + + @Override + public Headers getRequestHeaders() { + return requestHeaders; + } + + @Override + public Headers getResponseHeaders() { + return responseHeaders; + } + + @Override + public URI getRequestURI() { + return URI.create(httpServletRequest.getRequestURI()); + } + + @Override + public String getRequestMethod() { + return httpServletRequest.getMethod(); + } + + @Override + public HttpContext getHttpContext() { + return httpContext; + } + + @Override + public void close() { + try { + httpServletRequest.getInputStream().close(); + } catch (IOException e) { + // TODO use proper logging + e.printStackTrace(); + } + try { + httpServletResponse.getOutputStream().close(); + } catch (IOException e) { + // TODO use proper logging + e.printStackTrace(); + } + + } + + @Override + public InputStream getRequestBody() { + try { + if (filteredIn != null) + return filteredIn; + else + return httpServletRequest.getInputStream(); + } catch (IOException e) { + throw new IllegalStateException("Cannot get request body", e); + } + } + + @Override + public OutputStream getResponseBody() { + try { + if (filteredOut != null) + return filteredOut; + else + return httpServletResponse.getOutputStream(); + } catch (IOException e) { + throw new IllegalStateException("Cannot get response body", e); + } + } + + @Override + public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + for (String headerName : responseHeaders.keySet()) { + for (String headerValue : responseHeaders.get(headerName)) { + httpServletResponse.addHeader(headerName, headerValue); + } + } + // TODO deal with content length etc. + httpServletResponse.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return new InetSocketAddress(httpServletRequest.getRemoteHost(), httpServletRequest.getRemotePort()); + } + + @Override + public int getResponseCode() { + return httpServletResponse.getStatus(); + } + + @Override + public InetSocketAddress getLocalAddress() { + return new InetSocketAddress(httpServletRequest.getLocalName(), httpServletRequest.getLocalPort()); + } + + @Override + public String getProtocol() { + return httpServletRequest.getProtocol(); + } + + @Override + public Object getAttribute(String name) { + return httpServletRequest.getAttribute(name); + } + + @Override + public void setAttribute(String name, Object value) { + httpServletRequest.setAttribute(name, value); + } + + @Override + public void setStreams(InputStream i, OutputStream o) { + if (i != null) + filteredIn = i; + if (o != null) + filteredOut = o; + + } + + @Override + public HttpPrincipal getPrincipal() { + return principal; + } + + void setPrincipal(HttpPrincipal principal) { + this.principal = principal; + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java new file mode 100644 index 000000000..f0e11f8b9 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java @@ -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 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 en = request.getHeaderNames(); + while (en.hasMoreElements()) { + String header = en.nextElement(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) + buf.append(" " + header + ": " + values.nextElement()); + buf.append('\n'); + } + + // attributed + Enumeration an = request.getAttributeNames(); + while (an.hasMoreElements()) { + String attr = an.nextElement(); + Object value = request.getAttribute(attr); + buf.append(" " + attr + ": " + value); + buf.append('\n'); + } + log.debug("\n" + buf); + } + + private HttpUtils() { + + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java new file mode 100644 index 000000000..2b2ffcb10 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java @@ -0,0 +1,133 @@ +package org.argeo.cms.servlet.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collection; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.osgi.FilterRequirement; +import org.argeo.cms.osgi.PublishNamespace; +import org.argeo.cms.util.StreamUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; +import org.osgi.framework.VersionRange; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.resource.Requirement; + +public class PkgServlet extends HttpServlet { + private static final long serialVersionUID = 7660824185145214324L; + + private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String pathInfo = req.getPathInfo(); + + String pkg, versionStr, file; + String[] parts = pathInfo.split("/"); + // first is always empty + if (parts.length == 4) { + pkg = parts[1]; + versionStr = parts[2]; + file = parts[3]; + } else if (parts.length == 3) { + pkg = parts[1]; + versionStr = null; + file = parts[2]; + } else { + throw new IllegalArgumentException("Unsupported path length " + pathInfo); + } + + FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); + String filter; + if (versionStr == null) { + filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; + } else { + if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range + VersionRange versionRange = new VersionRange(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" + + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; + + } else { + Version version = new Version(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" + + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; + } + } + Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); + Collection packages = frameworkWiring.findProviders(requirement); + if (packages.isEmpty()) { + resp.sendError(404); + return; + } + + // TODO verify that it works with multiple versions + SortedMap sorted = new TreeMap<>(); + for (BundleCapability capability : packages) { + sorted.put(capability.getRevision().getVersion(), capability); + } + + Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); + String entryPath = '/' + pkg.replace('.', '/') + '/' + file; + URL internalURL = bundle.getResource(entryPath); + if (internalURL == null) { + resp.sendError(404); + return; + } + + // Resource found, we now check whether it can be published + boolean publish = false; + BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); + capabilities: for (BundleCapability bundleCapability : bundleWiring + .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { + Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); + if (publishedPkg != null) { + if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { + Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); + if (publishedFile == null) { + publish = true; + break capabilities; + } else { + String[] publishedFiles = publishedFile.toString().split(","); + for (String pattern : publishedFiles) { + if (pattern.startsWith("*.")) { + String ext = pattern.substring(1); + if (file.endsWith(ext)) { + publish = true; + break capabilities; + } + } else { + if (publishedFile.equals(file)) { + publish = true; + break capabilities; + } + } + } + } + } + } + } + + if (!publish) { + resp.sendError(404); + return; + } + + try (InputStream in = internalURL.openStream()) { + StreamUtils.copy(in, resp.getOutputStream()); + } + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java new file mode 100644 index 000000000..288ee268c --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java @@ -0,0 +1,24 @@ +package org.argeo.cms.servlet.internal; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RobotServlet extends HttpServlet { + private static final long serialVersionUID = 7935661175336419089L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + PrintWriter writer = response.getWriter(); + writer.append("User-agent: *\n"); + writer.append("Disallow:\n"); + response.setHeader("Content-Type", "text/plain"); + writer.flush(); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java new file mode 100644 index 000000000..4dfdc5d21 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java @@ -0,0 +1,135 @@ +package org.argeo.cms.websocket.server; + +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.websocket.Extension; +import javax.websocket.HandshakeResponse; +import javax.websocket.server.HandshakeRequest; +import javax.websocket.server.ServerEndpointConfig; +import javax.websocket.server.ServerEndpointConfig.Configurator; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; +import org.argeo.cms.auth.RemoteAuthSession; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.servlet.CmsServletContext; + +/** + * Disabled until third party issues are solved.. Customises + * the initialisation of a new web socket. + */ +public class CmsWebSocketConfigurator extends Configurator { + + private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class); + + private final String httpAuthRealm = "Argeo"; + + @Override + public boolean checkOrigin(String originHeaderValue) { + return true; + } + + @Override + public T getEndpointInstance(Class endpointClass) throws InstantiationException { + try { + return endpointClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get endpoint instance", e); + } + } + + @Override + public List getNegotiatedExtensions(List installed, List requested) { + return requested; + } + + @Override + public String getNegotiatedSubprotocol(List supported, List requested) { + if ((requested == null) || (requested.size() == 0)) + return ""; + if ((supported == null) || (supported.isEmpty())) + return ""; + for (String possible : requested) { + if (possible == null) + continue; + if (supported.contains(possible)) + return possible; + } + return ""; + } + + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { +// if (true) +// return; + + WebSocketHandshakeRequest remoteAuthRequest = new WebSocketHandshakeRequest(request); + WebSocketHandshakeResponse remoteAuthResponse = new WebSocketHandshakeResponse(response); +// RemoteAuthSession httpSession = new ServletHttpSession( +// (javax.servlet.http.HttpSession) request.getHttpSession()); + RemoteAuthSession remoteAuthSession = remoteAuthRequest.getSession(); + if (log.isDebugEnabled() && remoteAuthSession != null) + log.debug("Web socket HTTP session id: " + remoteAuthSession.getId()); + +// if (remoteAuthSession == null) { +// rejectResponse(response, null); +// } + ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader()); + LoginContext lc; + try { + lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse)); + lc.login(); + } catch (LoginException e) { + if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) { + int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest, remoteAuthResponse, httpAuthRealm, + true); +// remoteAuthResponse.setHeader("Status-Code", Integer.toString(statusCode)); + return; + } else { + lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse); + } + if (lc == null) { + rejectResponse(response, e); + return; + } + } finally { + Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader); + } + +// Subject subject = lc.getSubject(); +// Subject.doAs(subject, new PrivilegedAction() { +// +// @Override +// public Void run() { +// // TODO also set login context in order to log out ? +// RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest); +// return null; +// } +// +// }); + } + + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { + return true; + } + + /** + * Behaviour when the web socket could not be authenticated. Throws an + * {@link IllegalStateException} by default. + * + * @param e can be null + */ + protected void rejectResponse(HandshakeResponse response, Exception e) { + response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList()); + // violent implementation, as suggested in + // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake +// throw new IllegalStateException("Web socket cannot be authenticated"); + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java new file mode 100644 index 000000000..defc59efc --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java @@ -0,0 +1,63 @@ +package org.argeo.cms.websocket.server; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.util.Map; + +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnOpen; +import javax.websocket.RemoteEndpoint; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; + +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsEventSubscriber; +import org.argeo.api.cms.CmsLog; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +@ServerEndpoint(value = "/cms/status/event/{topic}", configurator = CmsWebSocketConfigurator.class) +public class EventEndpoint implements CmsEventSubscriber { + private final static CmsLog log = CmsLog.getLog(EventEndpoint.class); + private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext(); + + private RemoteEndpoint.Basic remote; + private CmsEventBus cmsEventBus; + +// private String topic = "cms"; + + @OnOpen + public void onOpen(Session session, @PathParam("topic") String topic) { + if (bc != null) { + cmsEventBus = bc.getService(bc.getServiceReference(CmsEventBus.class)); + cmsEventBus.addEventSubscriber(topic, this); + } + remote = session.getBasicRemote(); + + } + + @OnClose + public void onClose(@PathParam("topic") String topic) { + cmsEventBus.removeEventSubscriber(topic, this); + } + + @Override + public void onEvent(String topic, Map properties) { + try { + remote.sendText(topic + ": " + properties); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @OnError + public void onError(Throwable e) { + if (e instanceof ClosedChannelException) { + // ignore, as it probably means ping was closed on the other side + return; + } + log.error("Cannot process ping", e); + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java new file mode 100644 index 000000000..dcbce67b1 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java @@ -0,0 +1,22 @@ +package org.argeo.cms.websocket.server; + +import java.nio.channels.ClosedChannelException; + +import javax.websocket.OnError; +import javax.websocket.server.ServerEndpoint; + +import org.argeo.api.cms.CmsLog; + +@ServerEndpoint(value = "/cms/status/ping", configurator = PublicWebSocketConfigurator.class) +public class PingEndpoint { + private final static CmsLog log = CmsLog.getLog(PingEndpoint.class); + + @OnError + public void onError(Throwable e) { + if (e instanceof ClosedChannelException) { + // ignore, as it probably means ping was closed on the other side + return; + } + log.error("Cannot process ping", e); + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PublicWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PublicWebSocketConfigurator.java new file mode 100644 index 000000000..e3e17cf05 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PublicWebSocketConfigurator.java @@ -0,0 +1,13 @@ +package org.argeo.cms.websocket.server; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; + +public class PublicWebSocketConfigurator extends CmsWebSocketConfigurator { + + @Override + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { + return false; + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java new file mode 100644 index 000000000..a8466fee2 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java @@ -0,0 +1,55 @@ +package org.argeo.cms.websocket.server; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; + +import org.argeo.api.cms.CmsState; +import org.argeo.cms.CmsDeployProperty; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +public class StatusHandler implements WebsocketEndpoints, HttpHandler { + private CmsState cmsState; + + @Override + public Set> getEndPoints() { + Set> res = new HashSet<>(); + res.add(PingEndpoint.class); + res.add(EventEndpoint.class); + res.add(TestEndpoint.class); + return res; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + + StringJoiner sb = new StringJoiner("\n"); + CmsDeployProperty[] deployProperties = CmsDeployProperty.values(); + Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name())); + for (CmsDeployProperty deployProperty : deployProperties) { + List values = cmsState.getDeployProperties(deployProperty.getProperty()); + for (int i = 0; i < values.size(); i++) { + String value = values.get(i); + if (value != null) { + String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value; + sb.add(line); + } + } + } + + byte[] msg = sb.toString().getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, msg.length); + exchange.getResponseBody().write(msg); + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java new file mode 100644 index 000000000..f0c7fca3a --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java @@ -0,0 +1,183 @@ +package org.argeo.cms.websocket.server; + +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.websocket.CloseReason; +import javax.websocket.EndpointConfig; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.RemoteEndpoint; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; + +import org.argeo.api.acr.ldap.NamingUtils; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.integration.CmsExceptionsChain; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** Provides WebSocket access. */ +@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class) +public class TestEndpoint implements EventHandler { + private final static CmsLog log = CmsLog.getLog(TestEndpoint.class); + + final static String TOPICS_BASE = "/test"; + final static String INPUT = "input"; + final static String TOPIC = "topic"; + final static String VIEW_UID = "viewUid"; + final static String COMPUTATION_UID = "computationUid"; + final static String MESSAGES = "messages"; + final static String ERRORS = "errors"; + + final static String EXCEPTION = "exception"; + final static String MESSAGE = "message"; + + private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext(); + + private String wsSessionId; + private RemoteEndpoint.Basic remote; + private ServiceRegistration eventHandlerSr; + + // json + private ObjectMapper objectMapper = new ObjectMapper(); + + private WebSocketView view; + + @OnOpen + public void onOpen(Session session, EndpointConfig endpointConfig) { + Map> parameters = NamingUtils.queryToMap(session.getRequestURI()); + String path = NamingUtils.getQueryValue(parameters, "path"); + log.debug("WS Path: " + path); + + wsSessionId = session.getId(); + + // 24h timeout + session.setMaxIdleTimeout(1000 * 60 * 60 * 24); + + Map userProperties = session.getUserProperties(); + Subject subject = null; +// AccessControlContext accessControlContext = (AccessControlContext) userProperties +// .get(ServletContextHelper.REMOTE_USER); +// Subject subject = Subject.getSubject(accessControlContext); +// // Deal with authentication failure +// if (subject == null) { +// try { +// CloseReason.CloseCode closeCode = new CloseReason.CloseCode() { +// +// @Override +// public int getCode() { +// return 4001; +// } +// }; +// session.close(new CloseReason(closeCode, "Unauthorized")); +// if (log.isTraceEnabled()) +// log.trace("Unauthorized web socket " + wsSessionId + ". Closing with code " + closeCode.getCode() +// + "."); +// return; +// } catch (IOException e) { +// // silent +// } +// return;// ignore +// } + + if (log.isDebugEnabled()) + log.debug("WS#" + wsSessionId + " open for: " + subject); + remote = session.getBasicRemote(); + view = new WebSocketView(subject); + + // OSGi events + String[] topics = new String[] { TOPICS_BASE + "/*" }; + Hashtable ht = new Hashtable<>(); + ht.put(EventConstants.EVENT_TOPIC, topics); + ht.put(EventConstants.EVENT_FILTER, "(" + VIEW_UID + "=" + view.getUid() + ")"); + eventHandlerSr = bc.registerService(EventHandler.class, this, ht); + + if (log.isDebugEnabled()) + log.debug("New view " + view.getUid() + " opened, via web socket."); + } + + @OnMessage + public void onWebSocketText(@PathParam("topic") String topic, Session session, String message) + throws JsonMappingException, JsonProcessingException { + try { + if (log.isTraceEnabled()) + log.trace("WS#" + view.getUid() + " received:\n" + message + "\n"); +// JsonNode jsonNode = objectMapper.readTree(message); +// String topic = jsonNode.get(TOPIC).textValue(); + + final String computationUid = null; +// if (MY_TOPIC.equals(topic)) { +// view.checkRole(SPECIFIC_ROLE); +// computationUid= process(); +// } + remote.sendText("ACK " + topic); + } catch (Exception e) { + log.error("Error when receiving web socket message", e); + sendSystemErrorMessage(e); + } + } + + @OnClose + public void onWebSocketClose(CloseReason reason) { + if (eventHandlerSr != null) + eventHandlerSr.unregister(); + if (view != null && log.isDebugEnabled()) + log.debug("WS#" + view.getUid() + " closed: " + reason); + } + + @OnError + public void onWebSocketError(Throwable cause) { + if (view != null) { + log.error("WS#" + view.getUid() + " ERROR", cause); + } else { + if (log.isTraceEnabled()) + log.error("Error in web socket session " + wsSessionId, cause); + } + } + + @Override + public void handleEvent(Event event) { + try { + Object uid = event.getProperty(COMPUTATION_UID); + Exception exception = (Exception) event.getProperty(EXCEPTION); + if (exception != null) { + CmsExceptionsChain systemErrors = new CmsExceptionsChain(exception); + String sent = systemErrors.toJsonString(objectMapper); + remote.sendText(sent); + return; + } + String topic = event.getTopic(); + if (log.isTraceEnabled()) + log.trace("WS#" + view.getUid() + " " + topic + ": notify event " + topic + "#" + uid + ", " + event); + } catch (Exception e) { + log.error("Error when handling event for WebSocket", e); + sendSystemErrorMessage(e); + } + + } + + /** Sends an error message in JSON format. */ + protected void sendSystemErrorMessage(Exception e) { + CmsExceptionsChain systemErrors = new CmsExceptionsChain(e); + try { + if (remote != null) + remote.sendText(systemErrors.toJsonString(objectMapper)); + } catch (Exception e1) { + log.error("Cannot send WebSocket system error messages " + systemErrors, e1); + } + } +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java new file mode 100644 index 000000000..31bcf9298 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java @@ -0,0 +1,82 @@ +package org.argeo.cms.websocket.server; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import javax.servlet.http.HttpSession; +import javax.websocket.server.HandshakeRequest; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthSession; +import org.argeo.cms.servlet.ServletHttpSession; + +public class WebSocketHandshakeRequest implements RemoteAuthRequest { + private final HandshakeRequest handshakeRequest; + private final HttpSession httpSession; + + private Map attributes = new HashMap<>(); + + public WebSocketHandshakeRequest(HandshakeRequest handshakeRequest) { + Objects.requireNonNull(handshakeRequest); + this.handshakeRequest = handshakeRequest; + this.httpSession = (HttpSession) handshakeRequest.getHttpSession(); +// Objects.requireNonNull(this.httpSession); + } + + @Override + public RemoteAuthSession getSession() { + if (httpSession == null) + return null; + return new ServletHttpSession(httpSession); + } + + @Override + public RemoteAuthSession createSession() { + throw new UnsupportedOperationException(); + } + + @Override + public Locale getLocale() { + // TODO check Accept-Language header + return Locale.getDefault(); + } + + @Override + public Object getAttribute(String key) { + return attributes.get(key); + } + + @Override + public void setAttribute(String key, Object object) { + attributes.put(key, object); + } + + @Override + public String getHeader(String key) { + List values = handshakeRequest.getHeaders().get(key); + if (values.size() == 0) + return null; + if (values.size() > 1) + throw new IllegalStateException("More that one value for " + key + ": " + values); + return values.get(0); + } + + @Override + public String getRemoteAddr() { + throw new UnsupportedOperationException(); + } + + @Override + public int getLocalPort() { + throw new UnsupportedOperationException(); + } + + @Override + public int getRemotePort() { + throw new UnsupportedOperationException(); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java new file mode 100644 index 000000000..b003c6372 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java @@ -0,0 +1,29 @@ +package org.argeo.cms.websocket.server; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.websocket.HandshakeResponse; + +import org.argeo.cms.auth.RemoteAuthResponse; + +public class WebSocketHandshakeResponse implements RemoteAuthResponse { + private final HandshakeResponse handshakeResponse; + + public WebSocketHandshakeResponse(HandshakeResponse handshakeResponse) { + this.handshakeResponse = handshakeResponse; + } + + @Override + public void setHeader(String headerName, String value) { + handshakeResponse.getHeaders().put(headerName, Collections.singletonList(value)); + } + + @Override + public void addHeader(String headerName, String value) { + List values = handshakeResponse.getHeaders().getOrDefault(headerName, new ArrayList<>()); + values.add(value); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketTest.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketTest.java new file mode 100644 index 000000000..b10bcfda2 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketTest.java @@ -0,0 +1,35 @@ +package org.argeo.cms.websocket.server; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +/** Tests connectivity to the web socket server. */ +public class WebSocketTest { + + public static void main(String[] args) throws Exception { + CompletableFuture received = new CompletableFuture<>(); + WebSocket.Listener listener = new WebSocket.Listener() { + + public CompletionStage onText(WebSocket webSocket, CharSequence message, boolean last) { + System.out.println(message); + CompletionStage res = CompletableFuture.completedStage(message.toString()); + received.complete(true); + return res; + } + }; + + HttpClient client = HttpClient.newHttpClient(); + CompletableFuture ws = client.newWebSocketBuilder() + .buildAsync(URI.create("ws://localhost:7070/cms/status/test/my%20topic?path=my%2Frelative%2Fpath"), listener); + WebSocket webSocket = ws.get(); + webSocket.sendText("TEST", true); + + received.get(10, TimeUnit.SECONDS); + webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketView.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketView.java new file mode 100644 index 000000000..736631b10 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketView.java @@ -0,0 +1,60 @@ +package org.argeo.cms.websocket.server; + +import java.security.Principal; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import org.osgi.service.useradmin.Role; + +/** + * Abstraction of a single Frontend view, that is a web browser page. There can + * be multiple views within one single authenticated HTTP session. + */ +public class WebSocketView { + private final String uid; + private Subject subject; + + public WebSocketView(Subject subject) { + this.uid = UUID.randomUUID().toString(); + this.subject = subject; + } + + public String getUid() { + return uid; + } + + public Set getRoles() { + return roles(subject); + } + + public boolean isInRole(String role) { + return getRoles().contains(role); + } + + public void checkRole(String role) { + checkRole(subject, role); + } + + public final static Set roles(Subject subject) { + Set roles = new HashSet(); + X500Principal principal = subject.getPrincipals(X500Principal.class).iterator().next(); + String username = principal.getName(); + roles.add(username); + for (Principal group : subject.getPrincipals()) { + if (group instanceof Role) + roles.add(group.getName()); + } + return roles; + } + + public static void checkRole(Subject subject, String role) { + Set roles = roles(subject); + if (!roles.contains(role)) + throw new IllegalStateException("User is not in role " + role); + } + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebsocketEndpoints.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebsocketEndpoints.java new file mode 100644 index 000000000..f7cd69384 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebsocketEndpoints.java @@ -0,0 +1,9 @@ +package org.argeo.cms.websocket.server; + +import java.util.Set; + +/** Configure web socket in Jetty without hard dependency. */ +public interface WebsocketEndpoints { + Set> getEndPoints(); + +} diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/package-info.java new file mode 100644 index 000000000..9dfb76645 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS websocket integration. */ +package org.argeo.cms.websocket.server; \ No newline at end of file diff --git a/org.argeo.cms.lib.jetty/.classpath b/org.argeo.cms.lib.jetty/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.lib.jetty/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.lib.jetty/.project b/org.argeo.cms.lib.jetty/.project new file mode 100644 index 000000000..132df7ff4 --- /dev/null +++ b/org.argeo.cms.lib.jetty/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.lib.jetty + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.lib.jetty/bnd.bnd b/org.argeo.cms.lib.jetty/bnd.bnd new file mode 100644 index 000000000..171059dfa --- /dev/null +++ b/org.argeo.cms.lib.jetty/bnd.bnd @@ -0,0 +1,5 @@ +Import-Package: \ +javax.servlet.http,\ +org.eclipse.jetty.server.handler,\ +org.eclipse.jetty.util.component,\ +* \ No newline at end of file diff --git a/org.argeo.cms.lib.jetty/build.properties b/org.argeo.cms.lib.jetty/build.properties new file mode 100644 index 000000000..62f58d95a --- /dev/null +++ b/org.argeo.cms.lib.jetty/build.properties @@ -0,0 +1,8 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . +additional.bundles = org.eclipse.jetty.io,\ + org.eclipse.jetty.security,\ + org.argeo.ext.slf4j,\ + org.slf4j.api diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java new file mode 100644 index 000000000..b0b348d9c --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java @@ -0,0 +1,79 @@ +package org.argeo.cms.jetty; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; + +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; + +/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */ +public class CmsJettyServer extends JettyHttpServer { + private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir"; + // Equinox compatibility + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + private Path tempDir; + + private CompletableFuture serverContainer = new CompletableFuture<>(); + + protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { + } + + @Override + public void start() { + try { + tempDir = Files.createTempDirectory("jetty"); + } catch (IOException e) { + throw new IllegalStateException("Cannot create temp dir", e); + } + super.start(); + } + + @Override + protected ServletContextHandler createRootContextHandler() { + ServletContextHandler servletContextHandler = new ServletContextHandler(); + servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, + Thread.currentThread().getContextClassLoader()); + servletContextHandler.setClassLoader(this.getClass().getClassLoader()); + servletContextHandler.setContextPath("/"); + + servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile()); + SessionHandler handler = new SessionHandler(); + handler.setMaxInactiveInterval(-1); + servletContextHandler.setSessionHandler(handler); + + JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + CmsJettyServer.this.serverContainer.complete(serverContainer); + } + }); + + return servletContextHandler; + } + + @Override + protected ServerContainer getRootServerContainer() { + return serverContainer.join(); + } + + @Override + protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException { + addServlets(servletContextHandler); + } + + /* + * WEB SOCKET + */ + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java new file mode 100644 index 000000000..1e64fe075 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java @@ -0,0 +1,64 @@ +package org.argeo.cms.jetty; + +import java.util.AbstractMap; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jetty.server.handler.ContextHandler; + +/** + * A {@link Map} implementation wrapping the attributes of a Jetty + * {@link ContextHandler}. + */ +class ContextHandlerAttributes extends AbstractMap { + private ContextHandler contextHandler; + + public ContextHandlerAttributes(ContextHandler contextHandler) { + super(); + this.contextHandler = contextHandler; + } + + @Override + public Set> entrySet() { + Set> entries = new HashSet<>(); + for (Enumeration keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) { + entries.add(new ContextAttributeEntry(keys.nextElement())); + } + return entries; + } + + @Override + public Object put(String key, Object value) { + Object previousValue = get(key); + contextHandler.setAttribute(key, value); + return previousValue; + } + + private class ContextAttributeEntry implements Map.Entry { + private final String key; + + public ContextAttributeEntry(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Object getValue() { + return contextHandler.getAttribute(key); + } + + @Override + public Object setValue(Object value) { + Object previousValue = getValue(); + contextHandler.setAttribute(key, value); + return previousValue; + } + + } +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java new file mode 100644 index 000000000..d6037ba8d --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java @@ -0,0 +1,84 @@ +package org.argeo.cms.jetty; + +import java.util.AbstractMap; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; + +import org.argeo.cms.servlet.httpserver.HttpContextServlet; +import org.argeo.cms.websocket.server.WebsocketEndpoints; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; + +import com.sun.net.httpserver.HttpHandler; + +/** + * An @{HttpContext} implementation based on a Jetty + * {@link ServletContextHandler}. + */ +class ContextHandlerHttpContext extends JettyHttpContext { + private final ServletContextHandler servletContextHandler; + private final ContextHandlerAttributes attributes; + + public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) { + super(httpServer, path); + + // Jetty context handler + this.servletContextHandler = new ServletContextHandler(); + servletContextHandler.setContextPath(path); + HttpContextServlet servlet = new HttpContextServlet(this); + servletContextHandler.addServlet(new ServletHolder(servlet), "/*"); + SessionHandler sessionHandler = new SessionHandler(); + // FIXME find a better default + sessionHandler.setMaxInactiveInterval(-1); + servletContextHandler.setSessionHandler(sessionHandler); + + attributes = new ContextHandlerAttributes(servletContextHandler); + } + + @Override + public void setHandler(HttpHandler handler) { + super.setHandler(handler); + + // web socket + if (handler instanceof WebsocketEndpoints) { + JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { + serverContainer.addEndpoint(clss); + } + } + }); + } + + if (getJettyHttpServer().isStarted()) + try { + servletContextHandler.start(); + } catch (Exception e) { + throw new IllegalStateException("Cannot start context handler", e); + } + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + protected ServletContextHandler getServletContextHandler() { + return servletContextHandler; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java new file mode 100644 index 000000000..551e54e05 --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java @@ -0,0 +1,87 @@ +package org.argeo.cms.jetty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.servlet.ServletContext; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; + +import org.argeo.cms.websocket.server.WebsocketEndpoints; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +/** + * An @{HttpContext} implementation based on Jetty. It supports web sockets if + * the handler implements {@link WebsocketEndpoints}. + */ +abstract class JettyHttpContext extends HttpContext { + private final JettyHttpServer httpServer; + private final String path; + private final List filters = new ArrayList<>(); + + private HttpHandler handler; + private Authenticator authenticator; + + public JettyHttpContext(JettyHttpServer httpServer, String path) { + this.httpServer = httpServer; + if (!path.endsWith("/")) + throw new IllegalArgumentException("Path " + path + " should end with a /"); + this.path = path; + } + + protected abstract ServletContextHandler getServletContextHandler(); + + @Override + public HttpHandler getHandler() { + return handler; + } + + @Override + public void setHandler(HttpHandler handler) { + if (this.handler != null) + throw new IllegalArgumentException("Handler is already set"); + Objects.requireNonNull(handler); + this.handler = handler; + } + + @Override + public String getPath() { + return path; + } + + @Override + public HttpServer getServer() { + return getJettyHttpServer(); + } + + protected JettyHttpServer getJettyHttpServer() { + return httpServer; + } + + @Override + public List getFilters() { + return filters; + } + + @Override + public Authenticator setAuthenticator(Authenticator auth) { + Authenticator previousAuthenticator = authenticator; + this.authenticator = auth; + return previousAuthenticator; + } + + @Override + public Authenticator getAuthenticator() { + return authenticator; + } + +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java new file mode 100644 index 000000000..363bbaebe --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java @@ -0,0 +1,358 @@ +package org.argeo.cms.jetty; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +import javax.servlet.ServletException; +import javax.websocket.server.ServerContainer; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.http.server.HttpServerUtils; +import org.eclipse.jetty.http.UriCompliance; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.util.thread.ExecutorThreadPool; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; + +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; + +/** An {@link HttpServer} implementation based on Jetty. */ +public class JettyHttpServer extends HttpsServer { + private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class); + + private static final int DEFAULT_IDLE_TIMEOUT = 30000; + + private Server server; + + protected ServerConnector httpConnector; + protected ServerConnector httpsConnector; + + private InetSocketAddress httpAddress; + private InetSocketAddress httpsAddress; + + private ThreadPoolExecutor executor; + + private HttpsConfigurator httpsConfigurator; + + private final Map contexts = new TreeMap<>(); + + private ServletContextHandler rootContextHandler; + protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + + private boolean started; + + private CmsState cmsState; + + @Override + public void bind(InetSocketAddress addr, int backlog) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void start() { + try { + + ThreadPool threadPool = null; + if (executor != null) { + threadPool = new ExecutorThreadPool(executor); + } else { + // TODO make it configurable + threadPool = new QueuedThreadPool(10, 1); + } + + server = new Server(threadPool); + + configureConnectors(); + + if (httpConnector != null) { + httpConnector.open(); + server.addConnector(httpConnector); + } + + if (httpsConnector != null) { + httpsConnector.open(); + server.addConnector(httpsConnector); + } + + // holder + + // context + rootContextHandler = createRootContextHandler(); + // httpContext.addServlet(holder, "/*"); + if (rootContextHandler != null) + configureRootContextHandler(rootContextHandler); + + if (rootContextHandler != null && !contexts.containsKey("/")) + contextHandlerCollection.addHandler(rootContextHandler); + + server.setHandler(contextHandlerCollection); + + // + // START + server.start(); + // + + // Addresses + String httpHost = getDeployProperty(CmsDeployProperty.HOST); + String fallBackHostname = cmsState != null ? cmsState.getHostname() : "::1"; + if (httpConnector != null) { + httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, + httpConnector.getLocalPort()); + } else if (httpsConnector != null) { + httpsAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, + httpsConnector.getLocalPort()); + } + // Clean up + Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown")); + + log.info(httpPortsMsg()); + started = true; + } catch (Exception e) { + stop(); + throw new IllegalStateException("Cannot start Jetty HTTP server", e); + } + } + + protected void configureConnectors() { + String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT); + String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT); + if (httpPortStr != null && httpsPortStr != null) + throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both"); + if (httpPortStr == null && httpsPortStr == null) + throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured"); + + /// TODO make it more generic + String httpHost = getDeployProperty(CmsDeployProperty.HOST); + + // try { + if (httpPortStr != null || httpsPortStr != null) { + // TODO deal with hostname resolving taking too much time +// String fallBackHostname = InetAddress.getLocalHost().getHostName(); + + boolean httpEnabled = httpPortStr != null; + boolean httpsEnabled = httpsPortStr != null; + + if (httpEnabled) { + HttpConfiguration httpConfiguration = new HttpConfiguration(); + + if (httpsEnabled) {// not supported anymore to have both http and https, but it may change again + int httpsPort = Integer.parseInt(httpsPortStr); + httpConfiguration.setSecureScheme("https"); + httpConfiguration.setSecurePort(httpsPort); + } + + int httpPort = Integer.parseInt(httpPortStr); + httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration)); + httpConnector.setPort(httpPort); + httpConnector.setHost(httpHost); + httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT); + + } + + if (httpsEnabled) { + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + // sslContextFactory.setKeyStore(KeyS) + + sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE)); + sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE)); + sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD)); + // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD)); + sslContextFactory.setProtocol("TLS"); + + sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE)); + sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE)); + sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD)); + + String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH); + if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true))) + sslContextFactory.setWantClientAuth(true); + String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH); + if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true))) + sslContextFactory.setNeedClientAuth(true); + + // HTTPS Configuration + HttpConfiguration httpsConfiguration = new HttpConfiguration(); + httpsConfiguration.addCustomizer(new SecureRequestCustomizer()); + httpsConfiguration.setUriCompliance(UriCompliance.LEGACY); + + // HTTPS connector + httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), + new HttpConnectionFactory(httpsConfiguration)); + int httpsPort = Integer.parseInt(httpsPortStr); + httpsConnector.setPort(httpsPort); + httpsConnector.setHost(httpHost); + } + } + } + + @Override + public void stop(int delay) { + // TODO wait for processing to complete + stop(); + + } + + public void stop() { + try { + server.stop(); + // TODO delete temp dir + started = false; + } catch (Exception e) { + log.error("Cannot stop Jetty HTTP server", e); + } + + } + + @Override + public void setExecutor(Executor executor) { + if (!(executor instanceof ThreadPoolExecutor)) + throw new IllegalArgumentException("Only " + ThreadPoolExecutor.class.getName() + " are supported"); + this.executor = (ThreadPoolExecutor) executor; + } + + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public synchronized HttpContext createContext(String path, HttpHandler handler) { + HttpContext httpContext = createContext(path); + httpContext.setHandler(handler); + return httpContext; + } + + @Override + public synchronized HttpContext createContext(String path) { + if (!path.endsWith("/")) + path = path + "/"; + if (contexts.containsKey(path)) + throw new IllegalArgumentException("Context " + path + " already exists"); + + JettyHttpContext httpContext = new ServletHttpContext(this, path); + contexts.put(path, httpContext); + + contextHandlerCollection.addHandler(httpContext.getServletContextHandler()); + return httpContext; + } + + @Override + public synchronized void removeContext(String path) throws IllegalArgumentException { + if (!contexts.containsKey(path)) + throw new IllegalArgumentException("Context " + path + " does not exist"); + JettyHttpContext httpContext = contexts.remove(path); + if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) { + // TODO stop handler first? + contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler()); + } + } + + @Override + public synchronized void removeContext(HttpContext context) { + removeContext(context.getPath()); + } + + @Override + public InetSocketAddress getAddress() { + InetSocketAddress res = httpAddress != null ? httpAddress : httpsAddress; + if (res == null) + throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available"); + return res; + } + + @Override + public void setHttpsConfigurator(HttpsConfigurator config) { + this.httpsConfigurator = config; + } + + @Override + public HttpsConfigurator getHttpsConfigurator() { + return httpsConfigurator; + } + + protected String getDeployProperty(CmsDeployProperty deployProperty) { + return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty()) + : System.getProperty(deployProperty.getProperty()); + } + + private String httpPortsMsg() { + + return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "") + + (httpsConnector != null ? "HTTPS " + getHttpsPort() : ""); + } + + public Integer getHttpPort() { + if (httpConnector == null) + return null; + return httpConnector.getLocalPort(); + } + + public Integer getHttpsPort() { + if (httpsConnector == null) + return null; + return httpsConnector.getLocalPort(); + } + + protected ServletContextHandler createRootContextHandler() { + return null; + } + + protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException { + + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + + boolean isStarted() { + return started; + } + + ServletContextHandler getRootContextHandler() { + return rootContextHandler; + } + + ServerContainer getRootServerContainer() { + throw new UnsupportedOperationException(); + } + + public static void main(String... args) { + JettyHttpServer httpServer = new JettyHttpServer(); + System.setProperty("argeo.http.port", "8080"); + httpServer.createContext("/", (exchange) -> { + exchange.getResponseBody().write("Hello World!".getBytes()); + }); + httpServer.start(); + httpServer.createContext("/sub/context", (exchange) -> { + final String key = "count"; + Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key); + if (count == null) + exchange.getHttpContext().getAttributes().put(key, 0); + else + exchange.getHttpContext().getAttributes().put(key, count + 1); + StringBuilder sb = new StringBuilder(); + sb.append("Subcontext:"); + sb.append(" " + key + "=" + exchange.getHttpContext().getAttributes().get(key)); + sb.append(" relativePath=" + HttpServerUtils.relativize(exchange)); + exchange.getResponseBody().write(sb.toString().getBytes()); + }); + } +} diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java new file mode 100644 index 000000000..33611941d --- /dev/null +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java @@ -0,0 +1,64 @@ +package org.argeo.cms.jetty; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.servlet.httpserver.HttpContextServlet; +import org.argeo.cms.websocket.server.WebsocketEndpoints; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +import com.sun.net.httpserver.HttpHandler; + +/** + * A {@link JettyHttpContext} based on registering a servlet to the root handler + * of the {@link JettyHttpServer}, in order to integrate the sessions. + */ +public class ServletHttpContext extends JettyHttpContext { + private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class); + + private Map attributes = Collections.synchronizedMap(new HashMap<>()); + + public ServletHttpContext(JettyHttpServer httpServer, String path) { + super(httpServer, path); + + ServletContextHandler rootContextHandler = httpServer.getRootContextHandler(); + HttpContextServlet servlet = new HttpContextServlet(this); + rootContextHandler.addServlet(new ServletHolder(servlet), path + "*"); + } + + @Override + public void setHandler(HttpHandler handler) { + super.setHandler(handler); + + // web socket + if (handler instanceof WebsocketEndpoints) { + ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer(); + for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { + try { + serverContainer.addEndpoint(clss); + log.debug(() -> "Added web socket " + clss + " to " + getPath()); + } catch (DeploymentException e) { + log.error("Cannot deploy Web Socket " + clss, e); + } + } + } + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + protected ServletContextHandler getServletContextHandler() { + return getJettyHttpServer().getRootContextHandler(); + } + +} diff --git a/org.argeo.cms.lib.sshd/.classpath b/org.argeo.cms.lib.sshd/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.lib.sshd/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.lib.sshd/.gitignore b/org.argeo.cms.lib.sshd/.gitignore new file mode 100644 index 000000000..7fb0c180c --- /dev/null +++ b/org.argeo.cms.lib.sshd/.gitignore @@ -0,0 +1,3 @@ +/hostkey.ser +/id_rsa +/id_rsa.pub diff --git a/org.argeo.cms.lib.sshd/.project b/org.argeo.cms.lib.sshd/.project new file mode 100644 index 000000000..588b82996 --- /dev/null +++ b/org.argeo.cms.lib.sshd/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.lib.sshd + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml b/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml new file mode 100644 index 000000000..987b97745 --- /dev/null +++ b/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms.lib.sshd/bnd.bnd b/org.argeo.cms.lib.sshd/bnd.bnd new file mode 100644 index 000000000..85546f671 --- /dev/null +++ b/org.argeo.cms.lib.sshd/bnd.bnd @@ -0,0 +1,10 @@ +Import-Package: \ +org.apache.sshd.server.forward,\ +org.apache.sshd.common.forward,\ +org.apache.sshd.common.channel,\ +org.apache.sshd.common.helpers,\ +org.apache.sshd.common.file.util,\ +* + +Service-Component: \ +OSGI-INF/cmsSshServer.xml diff --git a/org.argeo.cms.lib.sshd/build.properties b/org.argeo.cms.lib.sshd/build.properties new file mode 100644 index 000000000..a04ec77ec --- /dev/null +++ b/org.argeo.cms.lib.sshd/build.properties @@ -0,0 +1,9 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/ +source.. = src/ +additional.bundles = org.slf4j.api,\ + org.argeo.ext.slf4j,\ + org.apache.tomcat.jni + \ No newline at end of file diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java new file mode 100644 index 000000000..d2fc89f79 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java @@ -0,0 +1,168 @@ +package org.argeo.cms.bc; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.math.BigInteger; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Date; + +import javax.security.auth.x500.X500Principal; + +import org.argeo.api.cms.CmsLog; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.PKCSException; + +/** Utilities around the BouncyCastle crypto library. */ +public class BcUtils { + private final static CmsLog log = CmsLog.getLog(BcUtils.class); + + private final static String BC_SECURITY_PROVIDER; + static { + Security.addProvider(new BouncyCastleProvider()); + BC_SECURITY_PROVIDER = "BC"; + } + + public static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) { + // for (Provider provider : Security.getProviders()) + // System.out.println(provider.getName()); + // File keyStoreFile = keyStorePath.toFile(); + char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length); + if (!Files.exists(keyStorePath)) { + try { + Files.createDirectories(keyStorePath.getParent()); + KeyStore keyStore = getKeyStore(keyStorePath, keyStorePassword, keyStoreType); + generateSelfSignedCertificate(keyStore, + new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"), + 1024, keyPwd); + saveKeyStore(keyStorePath, keyStorePassword, keyStore); + if (log.isDebugEnabled()) + log.debug("Created self-signed unsecure keystore " + keyStorePath); + } catch (Exception e) { + try { + if (Files.size(keyStorePath) == 0) + Files.delete(keyStorePath); + } catch (IOException e1) { + // silent + } + log.error("Cannot create keystore " + keyStorePath, e); + } + } else { + throw new IllegalStateException("Keystore " + keyStorePath + " already exists"); + } + } + + public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal, + int keySize, char[] keyPassword) { + try { + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC_SECURITY_PROVIDER); + kpGen.initialize(keySize, new SecureRandom()); + KeyPair pair = kpGen.generateKeyPair(); + Date notBefore = new Date(System.currentTimeMillis() - 10000); + Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000); + BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); + X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore, + notAfter, x500Principal, pair.getPublic()); + ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption") + .setProvider(BC_SECURITY_PROVIDER).build(pair.getPrivate()); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER) + .getCertificate(certGen.build(sigGen)); + cert.checkValidity(new Date()); + cert.verify(cert.getPublicKey()); + + keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert }); + return cert; + } catch (GeneralSecurityException | OperatorCreationException e) { + throw new RuntimeException("Cannot generate self-signed certificate", e); + } + } + + public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) { + try (PEMParser pemParser = new PEMParser(reader)) { + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_SECURITY_PROVIDER); + Object object = pemParser.readObject(); + PrivateKeyInfo privateKeyInfo; + if (object instanceof PKCS8EncryptedPrivateKeyInfo) { + if (keyPassword == null) + throw new IllegalArgumentException("A key password is required"); + InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword); + privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv); + } else if (object instanceof PrivateKeyInfo) { + privateKeyInfo = (PrivateKeyInfo) object; + } else { + throw new IllegalArgumentException("Unsupported format for private key"); + } + return converter.getPrivateKey(privateKeyInfo); + } catch (IOException | OperatorCreationException | PKCSException e) { + throw new RuntimeException("Cannot read private key", e); + } + } + + public static X509Certificate loadPemCertificate(Reader reader) { + try (PEMParser pemParser = new PEMParser(reader)) { + X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject(); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER) + .getCertificate(certHolder); + return cert; + } catch (IOException | CertificateException e) { + throw new RuntimeException("Cannot read private key", e); + } + } + + private static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) { + try { + KeyStore store = KeyStore.getInstance(keyStoreType, BC_SECURITY_PROVIDER); + if (Files.exists(keyStoreFile)) { + try (InputStream fis = Files.newInputStream(keyStoreFile)) { + store.load(fis, keyStorePassword); + } + } else { + store.load(null); + } + return store; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("Cannot load keystore " + keyStoreFile, e); + } + } + + private static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) { + try { + try (OutputStream fis = Files.newOutputStream(keyStoreFile)) { + keyStore.store(fis, keyStorePassword); + } + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("Cannot save keystore " + keyStoreFile, e); + } + } + + /** singleton */ + private BcUtils() { + } +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java new file mode 100644 index 000000000..c91ab4805 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java @@ -0,0 +1,210 @@ +package org.argeo.cms.ssh; + +import java.io.Console; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Scanner; +import java.util.Set; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.channel.ClientChannel; +import org.apache.sshd.client.channel.ClientChannelEvent; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.util.io.input.NoCloseInputStream; +import org.apache.sshd.common.util.io.output.NoCloseOutputStream; +import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider; +import org.argeo.api.cms.CmsLog; + +public abstract class AbstractSsh { + private final static CmsLog log = CmsLog.getLog(AbstractSsh.class); + + private SshClient sshClient; + private SftpFileSystemProvider sftpFileSystemProvider; + + private boolean passwordSet = false; + private ClientSession session; + + private SshKeyPair sshKeyPair; + + public synchronized SshClient getSshClient() { + if (sshClient == null) { + long begin = System.currentTimeMillis(); + sshClient = SshClient.setUpDefaultClient(); + sshClient.start(); + long duration = System.currentTimeMillis() - begin; + if (log.isDebugEnabled()) + log.debug("SSH client started in " + duration + " ms"); + Runtime.getRuntime().addShutdownHook(new Thread(() -> sshClient.stop(), "Stop SSH client")); + } + return sshClient; + } + + synchronized SftpFileSystemProvider getSftpFileSystemProvider() { + if (sftpFileSystemProvider == null) { + sftpFileSystemProvider = new SftpFileSystemProvider(sshClient); + } + return sftpFileSystemProvider; + } + + public void authenticate() { + if (sshKeyPair != null) { + session.addPublicKeyIdentity(sshKeyPair.asKeyPair()); + } else { + + if (!passwordSet) { + String password; + Console console = System.console(); + if (console == null) {// IDE + System.out.print("Password: "); + try (Scanner s = new Scanner(System.in)) { + password = s.next(); + } + } else { + console.printf("Password: "); + char[] pwd = console.readPassword(); + password = new String(pwd); + Arrays.fill(pwd, ' '); + } + session.addPasswordIdentity(password); + passwordSet = true; + } + } + verifyAuth(); + } + + public void verifyAuth() { + try { + session.auth().verify(1000l); + } catch (IOException e) { + throw new IllegalStateException("Cannot verify auth", e); + } + } + + public static char[] readPassword() { + Console console = System.console(); + if (console == null) {// IDE + System.out.print("Password: "); + try (Scanner s = new Scanner(System.in)) { + String password = s.next(); + return password.toCharArray(); + } + } else { + console.printf("Password: "); + char[] pwd = console.readPassword(); + return pwd; + } + } + + void addPassword(String password) { + session.addPasswordIdentity(password); + } + + void loadKey(String password) { + loadKey(password, System.getProperty("user.home") + "/.ssh/id_rsa"); + } + + void loadKey(String password, String keyPath) { +// try { +// KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath, +// FilePasswordProvider.of(password)); +// session.addPublicKeyIdentity(keyPair); +// } catch (IOException | GeneralSecurityException e) { +// throw new IllegalStateException(e); +// } + } + + void openSession(URI uri) { + openSession(uri.getUserInfo(), uri.getHost(), uri.getPort() > 0 ? uri.getPort() : null); + } + + void openSession(String login, String host, Integer port) { + if (session != null) + throw new IllegalStateException("Session is already open"); + + if (host == null) + host = "localhost"; + if (port == null) + port = 22; + if (login == null) + login = System.getProperty("user.name"); + String password = null; + int sepIndex = login.indexOf(':'); + if (sepIndex > 0) + if (sepIndex + 1 < login.length()) { + password = login.substring(sepIndex + 1); + login = login.substring(0, sepIndex); + } else { + throw new IllegalArgumentException("Illegal authority: " + login); + } + try { + ConnectFuture connectFuture = getSshClient().connect(login, host, port); + connectFuture.await(); + ClientSession session = connectFuture.getSession(); + if (password != null) { + session.addPasswordIdentity(password); + passwordSet = true; + } + this.session = session; + } catch (IOException e) { + throw new IllegalStateException("Cannot connect to " + host + ":" + port); + } + } + + public void closeSession() { + if (session == null) + throw new IllegalStateException("No session is open"); + try { + session.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + session = null; + } + } + + ClientSession getSession() { + return session; + } + + public void setSshKeyPair(SshKeyPair sshKeyPair) { + this.sshKeyPair = sshKeyPair; + } + + public static void openShell(AbstractSsh ssh) { + openShell(ssh.getSession()); + } + + public static void openShell(ClientSession session) { + try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) { + channel.setIn(new NoCloseInputStream(System.in)); + channel.setOut(new NoCloseOutputStream(System.out)); + channel.setErr(new NoCloseOutputStream(System.err)); + channel.open(); + + Set events = new HashSet<>(); + events.add(ClientChannelEvent.CLOSED); + channel.waitFor(events, 0); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + session.close(false); + } + } + + static URI toUri(String username, String host, int port) { + try { + if (username == null) + username = "root"; + return new URI("ssh://" + username + "@" + host + ":" + port); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot generate SSH URI to " + host + ":" + port + " for " + username, + e); + } + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java new file mode 100644 index 000000000..cde6c935a --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java @@ -0,0 +1,107 @@ +package org.argeo.cms.ssh; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.sshd.scp.server.ScpCommandFactory; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.apache.sshd.server.shell.ProcessShellFactory; +import org.argeo.cms.util.OS; + +/** A simple SSH server with some defaults. Supports SCP. */ +public class BasicSshServer { + private Integer port; + private Path hostKeyPath; + + private SshServer sshd = null; + + public BasicSshServer(Integer port, Path hostKeyPath) { + this.port = port; + this.hostKeyPath = hostKeyPath; + } + + public void init() { + try { + sshd = SshServer.setUpDefaultServer(); + sshd.setPort(port); + if (hostKeyPath == null) + throw new IllegalStateException("An SSH server key must be set"); + sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath)); + // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", + // "-l" })); + String[] shellCommand = OS.LOCAL.getDefaultShellCommand(); + // FIXME transfer args +// sshd.setShellFactory(new ProcessShellFactory(shellCommand)); + sshd.setShellFactory(new ProcessShellFactory(shellCommand[0], shellCommand)); + sshd.setCommandFactory(new ScpCommandFactory()); + + sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true)); + sshd.start(); + } catch (Exception e) { + throw new RuntimeException("Cannot start SSH server on port " + port, e); + } + } + + public void destroy() { + try { + sshd.stop(); + } catch (IOException e) { + throw new RuntimeException("Cannot stop SSH server on port " + port, e); + } + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public Path getHostKeyPath() { + return hostKeyPath; + } + + public void setHostKeyPath(Path hostKeyPath) { + this.hostKeyPath = hostKeyPath; + } + + public static void main(String[] args) { + int port = 2222; + Path hostKeyPath = Paths.get("hostkey.ser"); + try { + if (args.length > 0) + port = Integer.parseInt(args[0]); + if (args.length > 1) + hostKeyPath = Paths.get(args[1]); + } catch (Exception e1) { + printUsage(); + } + + BasicSshServer sshServer = new BasicSshServer(port, hostKeyPath); + sshServer.init(); + Runtime.getRuntime().addShutdownHook(new Thread("Shutdown SSH server") { + + @Override + public void run() { + sshServer.destroy(); + } + }); + try { + synchronized (sshServer) { + sshServer.wait(); + } + } catch (InterruptedException e) { + sshServer.destroy(); + } + + } + + public static void printUsage() { + System.out.println("java " + BasicSshServer.class.getName() + " [port] [server key path]"); + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java new file mode 100644 index 000000000..f5609a37d --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java @@ -0,0 +1,218 @@ +package org.argeo.cms.ssh; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.PosixFilePermission; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.sshd.common.config.keys.PublicKeyEntry; +import org.apache.sshd.common.forward.PortForwardingEventListener; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.util.net.SshdSocketAddress; +import org.apache.sshd.scp.server.ScpCommandFactory; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.auth.gss.GSSAuthenticator; +import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator; +import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator; +import org.apache.sshd.server.forward.AcceptAllForwardingFilter; +import org.apache.sshd.server.jaas.JaasPasswordAuthenticator; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.apache.sshd.server.shell.InteractiveProcessShellFactory; +import org.apache.sshd.sftp.server.SftpSubsystemFactory; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.CmsSshd; + +public class CmsSshServer implements CmsSshd { + private final static CmsLog log = CmsLog.getLog(CmsSshServer.class); + + private CmsState cmsState; + private SshServer sshd = null; + + private int port; + private String host; + + public void start() { + String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty()); + if (portStr == null) + return; // ignore + port = Integer.parseInt(portStr); + + host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty()); + + KeyPair nodeKeyPair = loadNodeKeyPair(); + + try { + // authorized keys + String authorizedKeysStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_AUTHORIZEDKEYS.getProperty()); + Path authorizedKeysPath = authorizedKeysStr != null ? Paths.get(authorizedKeysStr) + : AuthorizedKeysAuthenticator.getDefaultAuthorizedKeysFile(); + if (authorizedKeysStr != null && !Files.exists(authorizedKeysPath)) { + Files.createFile(authorizedKeysPath); + Set posixPermissions = new HashSet<>(); + posixPermissions.add(PosixFilePermission.OWNER_READ); + posixPermissions.add(PosixFilePermission.OWNER_WRITE); + Files.setPosixFilePermissions(authorizedKeysPath, posixPermissions); + + if (nodeKeyPair != null) + try { + String openSsshPublicKey = PublicKeyEntry.toString(nodeKeyPair.getPublic()); + try (Writer writer = Files.newBufferedWriter(authorizedKeysPath, StandardCharsets.US_ASCII, + StandardOpenOption.APPEND)) { + writer.write(openSsshPublicKey); + } + } catch (IOException e) { + log.error("Cannot add node public key to SSH authorized keys", e); + } + } + + // create server + sshd = SshServer.setUpDefaultServer(); + sshd.setPort(port); + if (host != null) + sshd.setHost(host); + + // host key + if (nodeKeyPair != null) { + sshd.setKeyPairProvider(KeyPairProvider.wrap(nodeKeyPair)); + } else { + Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH); + if (hostKeyPath == null) // TODO deal with no data area? + throw new IllegalStateException("An SSH server key must be set"); + sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath)); + } + + // tunnels + sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE); + sshd.addPortForwardingEventListener(new PortForwardingEventListener() { + + @Override + public void establishingExplicitTunnel(Session session, SshdSocketAddress local, + SshdSocketAddress remote, boolean localForwarding) throws IOException { + log.debug("Establishing tunnel " + local + ", " + remote); + } + + @Override + public void establishedExplicitTunnel(Session session, SshdSocketAddress local, + SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress, + Throwable reason) throws IOException { + log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress); + } + + @Override + public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException { + log.debug("Establishing dynamic tunnel " + local); + } + + @Override + public void establishedDynamicTunnel(Session session, SshdSocketAddress local, + SshdSocketAddress boundAddress, Throwable reason) throws IOException { + log.debug("Established dynamic tunnel " + local); + } + + }); + + // Authentication + // FIXME use strict, set proper permissions, etc. + sshd.setPublickeyAuthenticator( + new DefaultAuthorizedKeysAuthenticator("user.name", authorizedKeysPath, true)); + // sshd.setPublickeyAuthenticator(null); + // sshd.setKeyboardInteractiveAuthenticator(null); + JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator(); + jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName()); + sshd.setPasswordAuthenticator(jaasPasswordAuthenticator); + + boolean gssApi = false; + if (gssApi) { + Path krb5keyTab = cmsState.getDataPath("private/krb5.keytab"); + if (Files.exists(krb5keyTab)) { + // FIXME experimental + GSSAuthenticator gssAuthenticator = new GSSAuthenticator(); + gssAuthenticator.setKeytabFile(krb5keyTab.toString()); + gssAuthenticator.setServicePrincipalName("HTTP@" + host); + sshd.setGSSAuthenticator(gssAuthenticator); + } + } + + // shell + // TODO make it configurable + sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE); +// String[] shellCommand = OS.LOCAL.getDefaultShellCommand(); +// StringJoiner command = new StringJoiner(" "); +// for (String str : shellCommand) { +// command.add(str); +// } +// sshd.setShellFactory(new ProcessShellFactory(command.toString(), shellCommand)); + sshd.setCommandFactory(new ScpCommandFactory()); + + // SFTP + sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory())); + + // start + sshd.start(); + + log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : "")); + } catch (IOException e) { + throw new RuntimeException("Cannot start SSH server on port " + port, e); + } + + } + + public void stop() { + if (sshd == null) + return; + try { + sshd.stop(); + log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : "")); + } catch (IOException e) { + throw new RuntimeException("Cannot stop SSH server", e); + } + + } + + protected KeyPair loadNodeKeyPair() { + try { + char[] keyStorePassword = cmsState.getDeployProperty(CmsDeployProperty.SSL_PASSWORD.getProperty()) + .toCharArray(); + Path keyStorePath = Paths.get(cmsState.getDeployProperty(CmsDeployProperty.SSL_KEYSTORE.getProperty())); + String keyStoreType = cmsState.getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE.getProperty()); + + KeyStore store = KeyStore.getInstance(keyStoreType, "SunJSSE"); + try (InputStream fis = Files.newInputStream(keyStorePath)) { + store.load(fis, keyStorePassword); + } + return new KeyPair(store.getCertificate(CmsConstants.NODE).getPublicKey(), + (PrivateKey) store.getKey(CmsConstants.NODE, keyStorePassword)); + } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException + | CertificateException | IllegalArgumentException | UnrecoverableKeyException e) { + log.error("Cannot add node public key to SSH authorized keys", e); + return null; + } + + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java new file mode 100644 index 000000000..f6f447418 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java @@ -0,0 +1,42 @@ +package org.argeo.cms.ssh; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Path; + +import org.apache.sshd.sftp.client.fs.SftpFileSystem; + +/** Create an SFTP {@link FileSystem}. */ +public class Sftp extends AbstractSsh { + private URI uri; + + private SftpFileSystem fileSystem; + + public Sftp(String username, String host, int port) { + this(AbstractSsh.toUri(username, host, port)); + } + + public Sftp(URI uri) { + this.uri = uri; + openSession(uri); + } + + public FileSystem getFileSystem() { + if (fileSystem == null) { + try { + authenticate(); + fileSystem = getSftpFileSystemProvider().newFileSystem(getSession()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return fileSystem; + } + + public Path getBasePath() { + String p = uri.getPath() != null ? uri.getPath() : "/"; + return getFileSystem().getPath(p); + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java new file mode 100644 index 000000000..6e7b6ef2a --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java @@ -0,0 +1,81 @@ +package org.argeo.cms.ssh; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; + +/** Create an SSH shell. */ +public class Ssh extends AbstractSsh { + private final URI uri; + + public Ssh(String username, String host, int port) { + this(AbstractSsh.toUri(username, host, port)); + } + + public Ssh(URI uri) { + this.uri = uri; + openSession(uri); + } + + public static void main(String[] args) { + Options options = getOptions(); + CommandLineParser parser = new DefaultParser(); + try { + CommandLine line = parser.parse(options, args); + List remaining = line.getArgList(); + if (remaining.size() == 0) { + System.err.println("There must be at least one argument"); + printHelp(options); + System.exit(1); + } + URI uri = new URI("ssh://" + remaining.get(0)); + List command = new ArrayList<>(); + if (remaining.size() > 1) { + for (int i = 1; i < remaining.size(); i++) { + command.add(remaining.get(i)); + } + } + + // auth + Ssh ssh = new Ssh(uri); + ssh.authenticate(); + + if (command.size() == 0) {// shell + AbstractSsh.openShell(ssh.getSession()); + } else {// execute command + + } + ssh.closeSession(); + } catch (Exception exp) { + exp.printStackTrace(); + printHelp(options); + System.exit(1); + } finally { + + } + } + + public URI getUri() { + return uri; + } + + public static Options getOptions() { + Options options = new Options(); +// options.addOption("p", true, "port"); + options.addOption(Option.builder("p").hasArg().argName("port").desc("port of the SSH server").build()); + + return options; + } + + public static void printHelp(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("ssh [username@]hostname", options, true); + } +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java new file mode 100644 index 000000000..f5cbb0450 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java @@ -0,0 +1,205 @@ +package org.argeo.cms.ssh; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.spec.RSAPublicKeySpec; + +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.config.keys.PublicKeyEntry; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.PKCS8Generator; +import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.operator.OutputEncryptor; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; + +@SuppressWarnings("restriction") +public class SshKeyPair { + public final static String RSA_KEY_TYPE = "ssh-rsa"; + + private PublicKey publicKey; + private PrivateKey privateKey; + private KeyPair keyPair; + + public SshKeyPair(KeyPair keyPair) { + super(); + this.publicKey = keyPair.getPublic(); + this.privateKey = keyPair.getPrivate(); + this.keyPair = keyPair; + } + + public SshKeyPair(PublicKey publicKey, PrivateKey privateKey) { + super(); + this.publicKey = publicKey; + this.privateKey = privateKey; + this.keyPair = new KeyPair(publicKey, privateKey); + } + + public KeyPair asKeyPair() { + return keyPair; + } + + public String getPublicKeyAsOpenSshString() { + return PublicKeyEntry.toString(publicKey); + } + + public String getPrivateKeyAsPemString(char[] password) { + try { + Object obj; + + if (password != null) { + JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder( + PKCS8Generator.PBE_SHA1_3DES); + encryptorBuilder.setPasssword(password); + OutputEncryptor oe = encryptorBuilder.build(); + JcaPKCS8Generator gen = new JcaPKCS8Generator(privateKey, oe); + obj = gen.generate(); + } else { + obj = privateKey; + } + + StringWriter sw = new StringWriter(); + JcaPEMWriter pemWrt = new JcaPEMWriter(sw); + pemWrt.writeObject(obj); + pemWrt.close(); + return sw.toString(); + } catch (Exception e) { + throw new RuntimeException("Cannot convert private key", e); + } + } + + public static SshKeyPair loadOrGenerate(Path privateKeyPath, int size, char[] password) { + try { + SshKeyPair sshKeyPair; + if (Files.exists(privateKeyPath)) { +// String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII); + sshKeyPair = load( + new InputStreamReader(Files.newInputStream(privateKeyPath), StandardCharsets.US_ASCII), + password); + // TOD make sure public key is consistemt + } else { + sshKeyPair = generate(size); + Files.write(privateKeyPath, + sshKeyPair.getPrivateKeyAsPemString(password).getBytes(StandardCharsets.US_ASCII)); + Path publicKeyPath = privateKeyPath.resolveSibling(privateKeyPath.getFileName() + ".pub"); + Files.write(publicKeyPath, + sshKeyPair.getPublicKeyAsOpenSshString().getBytes(StandardCharsets.US_ASCII)); + } + return sshKeyPair; + } catch (IOException e) { + throw new RuntimeException("Cannot read or write private key " + privateKeyPath, e); + } + } + + public static SshKeyPair generate(int size) { + return generate(RSA_KEY_TYPE, size); + } + + public static SshKeyPair generate(String keyType, int size) { + try { + KeyPair keyPair = KeyUtils.generateKeyPair(keyType, size); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + return new SshKeyPair(publicKey, privateKey); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Cannot generate SSH key", e); + } + } + + public static SshKeyPair loadDefault(char[] password) { + Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa"); + // TODO try other formats + return load(privateKeyPath, password); + } + + public static SshKeyPair load(Path privateKeyPath, char[] password) { + try (Reader reader = Files.newBufferedReader(privateKeyPath)) { + return load(reader, password); + } catch (IOException e) { + throw new IllegalStateException("Cannot load private key from " + privateKeyPath, e); + } + + } + + public static SshKeyPair load(Reader reader, char[] password) { + try (PEMParser pemParser = new PEMParser(reader)) { + Object object = pemParser.readObject(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC"); + KeyPair kp; + if (object instanceof PEMEncryptedKeyPair) { + PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object; + PEMDecryptorProvider decryptorProvider = new BcPEMDecryptorProvider(password); + PEMKeyPair pemKp = ekp.decryptKeyPair(decryptorProvider); + kp = converter.getKeyPair(pemKp); + } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) { + // Encrypted key - we will use provided password + PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object; +// PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password); + InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder() + .build(password); + PrivateKeyInfo pkInfo = ckp.decryptPrivateKeyInfo(inputDecryptorProvider); + PrivateKey privateKey = converter.getPrivateKey(pkInfo); + + // generate public key + RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey; + RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(), + privk.getPublicExponent()); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + kp = new KeyPair(publicKey, privateKey); + } else { + // Unencrypted key - no password needed +// PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object; + PEMKeyPair pemKp = (PEMKeyPair) object; + kp = converter.getKeyPair(pemKp); + } + return new SshKeyPair(kp); + } catch (Exception e) { + throw new RuntimeException("Cannot load private key", e); + } + } + + public static void main(String args[]) { + Path privateKeyPath = Paths.get(System.getProperty("user.dir") + "/id_rsa"); + SshKeyPair skp = SshKeyPair.loadOrGenerate(privateKeyPath, 1024, null); + System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString()); + System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null)); + System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray())); + + StringReader reader = new StringReader(skp.getPrivateKeyAsPemString(null)); + skp = SshKeyPair.load(reader, null); + System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString()); + System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null)); + System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray())); + + reader = new StringReader(skp.getPrivateKeyAsPemString("demo".toCharArray())); + skp = SshKeyPair.load(reader, "demo".toCharArray()); + System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString()); + System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null)); + System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray())); + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java new file mode 100644 index 000000000..31e63411a --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java @@ -0,0 +1,154 @@ +package org.argeo.cms.ssh; + +import java.io.Console; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.util.Map; +import java.util.Scanner; + +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentFactory; +import org.apache.sshd.agent.local.LocalAgentFactory; +import org.apache.sshd.agent.unix.UnixAgentFactory; +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.config.keys.ClientIdentityLoader; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.sftp.client.fs.SftpFileSystem; +import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.util.StreamUtils; + +public class SshSync { + private final static CmsLog log = CmsLog.getLog(SshSync.class); + + public static void main(String[] args) { + + try (SshClient client = SshClient.setUpDefaultClient()) { + client.start(); + boolean osAgent = false; + SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory(); + // SshAgentFactory agentFactory = new LocalAgentFactory(); + client.setAgentFactory(agentFactory); + SshAgent sshAgent = agentFactory.createClient(null, client); + + String login = System.getProperty("user.name"); + String host = "localhost"; + int port = 22; + + if (!osAgent) { + String keyPath = "/home/" + login + "/.ssh/id_rsa"; + + String password; + Console console = System.console(); + if (console != null) { + password = new String(console.readPassword(keyPath + ": ")); + } else { + System.out.print(keyPath + ": "); + try (Scanner s = new Scanner(System.in)) { + password = s.next(); + } + } + NamedResource namedResource = new NamedResource() { + + @Override + public String getName() { + return keyPath; + } + }; + KeyPair keyPair = ClientIdentityLoader.DEFAULT + .loadClientIdentities(null, namedResource, FilePasswordProvider.of(password)).iterator().next(); + sshAgent.addIdentity(keyPair, "NO COMMENT"); + } + +// List> identities = sshAgent.getIdentities(); +// for (Map.Entry entry : identities) { +// System.out.println(entry.getValue() + " : " + entry.getKey()); +// } + + ConnectFuture connectFuture = client.connect(login, host, port); + connectFuture.await(); + ClientSession session = connectFuture.getSession(); + + try { + +// session.addPasswordIdentity(new String(password)); + session.auth().verify(1000l); + + SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client); + + SftpFileSystem fs = fsProvider.newFileSystem(session); + Path testPath = fs.getPath("/home/" + login + "/tmp"); + Files.list(testPath).forEach(System.out::println); + test(testPath); + + } finally { + client.stop(); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + static void test(Path testBase) { + try { + Path testPath = testBase.resolve("ssh-test.txt"); + Files.createFile(testPath); + log.debug("Created file " + testPath); + Files.delete(testPath); + log.debug("Deleted " + testPath); + String txt = "TEST\nTEST2\n"; + byte[] arr = txt.getBytes(); + Files.write(testPath, arr); + log.debug("Wrote " + testPath); + byte[] read = Files.readAllBytes(testPath); + log.debug("Read " + testPath); + Path testDir = testBase.resolve("testDir"); + log.debug("Resolved " + testDir); + // Copy + Files.createDirectory(testDir); + log.debug("Created directory " + testDir); + Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir")); + log.debug("Created sub directories " + subsubdir); + Path copiedFile = testDir.resolve("copiedFile.txt"); + log.debug("Resolved " + copiedFile); + Path relativeCopiedFile = testDir.relativize(copiedFile); + log.debug("Relative copied file " + relativeCopiedFile); + try (OutputStream out = Files.newOutputStream(copiedFile); + InputStream in = Files.newInputStream(testPath)) { + StreamUtils.copy(in, out); + } + log.debug("Copied " + testPath + " to " + copiedFile); + Files.delete(testPath); + log.debug("Deleted " + testPath); + byte[] copiedRead = Files.readAllBytes(copiedFile); + log.debug("Read " + copiedFile); + // Browse directories + DirectoryStream files = Files.newDirectoryStream(testDir); + int fileCount = 0; + Path listedFile = null; + for (Path file : files) { + fileCount++; + if (!Files.isDirectory(file)) + listedFile = file; + } + log.debug("Listed " + testDir); + // Generic attributes + Map attrs = Files.readAttributes(copiedFile, "*"); + log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/DefaultClientIdentityLoader.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/DefaultClientIdentityLoader.java new file mode 100644 index 000000000..9199198d7 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/DefaultClientIdentityLoader.java @@ -0,0 +1,54 @@ +package org.argeo.cms.ssh.cli; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Objects; + +import org.apache.sshd.client.config.keys.ClientIdentityLoader; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.io.IoUtils; +import org.apache.sshd.common.util.io.resource.PathResource; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** Separate class in order to avoit static field from Apache SSHD. */ +class DefaultClientIdentityLoader implements ClientIdentityLoader { + @Override + public boolean isValidLocation(NamedResource location) throws IOException { + Path path = toPath(location); + return Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS); + } + + @Override + public Iterable loadClientIdentities(SessionContext session, NamedResource location, + FilePasswordProvider provider) throws IOException, GeneralSecurityException { + Path path = toPath(location); + PathResource resource = new PathResource(path); + try (InputStream inputStream = resource.openInputStream()) { + return SecurityUtils.loadKeyPairIdentities(session, resource, inputStream, provider); + } + } + + @Override + public String toString() { + return "DEFAULT"; + } + + private Path toPath(NamedResource location) { + Objects.requireNonNull(location, "No location provided"); + + Path path = Paths + .get(ValidateUtils.checkNotNullAndNotEmpty(location.getName(), "No location value for %s", location)); + path = path.toAbsolutePath(); + path = path.normalize(); + return path; + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java new file mode 100644 index 000000000..287727544 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java @@ -0,0 +1,21 @@ +package org.argeo.cms.ssh.cli; + +import org.argeo.api.cli.CommandsCli; + +/** SSH command line interface. */ +public class SshCli extends CommandsCli { + public SshCli(String commandName) { + super(commandName); + addCommand("shell", new SshShell()); + } + + @Override + public String getDescription() { + return "SSH utilities."; + } + + public static void main(String[] args) { + mainImpl(new SshCli("ssh"), args); + } + +} diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java new file mode 100644 index 000000000..dffb440f1 --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java @@ -0,0 +1,122 @@ +package org.argeo.cms.ssh.cli; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.URI; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.sshd.agent.SshAgent; +import org.apache.sshd.agent.SshAgentFactory; +import org.apache.sshd.agent.local.LocalAgentFactory; +import org.apache.sshd.agent.unix.UnixAgentFactory; +import org.apache.sshd.common.NamedResource; +import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.argeo.api.cli.CommandArgsException; +import org.argeo.api.cli.DescribedCommand; +import org.argeo.cms.ssh.AbstractSsh; +import org.argeo.cms.ssh.Ssh; + +public class SshShell implements DescribedCommand { + private Option portOption; + + @Override + public Options getOptions() { + Options options = new Options(); + portOption = Option.builder().option("p").longOpt("port").hasArg().desc("port to connect to").build(); + options.addOption(portOption); + return options; + } + + @Override + public String apply(List args) { + CommandLine cl = toCommandLine(args); + String portStr = cl.getOptionValue(portOption); + if (portStr == null) + portStr = "22"; + + if (cl.getArgList().size() == 0) + throw new CommandArgsException("Host must be provided"); + String host = cl.getArgList().get(0); + + String uriStr = "ssh://" + host + ":" + portStr + "/"; + // System.out.println(uriStr); + URI uri = URI.create(uriStr); + + Ssh ssh = null; + try { + ssh = new Ssh(uri); + boolean osAgent; + SshAgent sshAgent; + try { + String sshAuthSockentEnv = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME); + if (sshAuthSockentEnv != null) { + ssh.getSshClient().getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, sshAuthSockentEnv); + SshAgentFactory agentFactory = new UnixAgentFactory(); + ssh.getSshClient().setAgentFactory(agentFactory); + sshAgent = agentFactory.createClient(null, ssh.getSshClient()); + osAgent = true; + } else { + osAgent = false; + } + } catch (Exception e) { + e.printStackTrace(); + osAgent = false; + } + + if (!osAgent) { + SshAgentFactory agentFactory = new LocalAgentFactory(); + ssh.getSshClient().setAgentFactory(agentFactory); + sshAgent = agentFactory.createClient(null, ssh.getSshClient()); + String keyPath = System.getProperty("user.home") + "/.ssh/id_rsa"; + + char[] keyPassword = AbstractSsh.readPassword(); + NamedResource namedResource = new NamedResource() { + + @Override + public String getName() { + return keyPath; + } + }; + KeyPair keyPair = new DefaultClientIdentityLoader() + .loadClientIdentities(null, namedResource, FilePasswordProvider.of(new String(keyPassword))) + .iterator().next(); + sshAgent.addIdentity(keyPair, "NO COMMENT"); + } + +// char[] keyPassword = AbstractSsh.readPassword(); +// SshKeyPair keyPair = SshKeyPair.loadDefault(keyPassword); +// Arrays.fill(keyPassword, '*'); +// ssh.setSshKeyPair(keyPair); +// ssh.authenticate(); + ssh.verifyAuth(); + + long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); + System.out.println("Ssh available in " + jvmUptime + " ms."); + + AbstractSsh.openShell(ssh); + } catch (IOException | GeneralSecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + if (ssh != null) + ssh.closeSession(); + } + return null; + } + + @Override + public String getUsage() { + return ""; + } + + @Override + public String getDescription() { + return "Opens a remote shell"; + } + +} \ No newline at end of file diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java new file mode 100644 index 000000000..9555662ad --- /dev/null +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java @@ -0,0 +1,2 @@ +/** SSH support. */ +package org.argeo.cms.ssh; \ No newline at end of file diff --git a/org.argeo.cms.pgsql/.classpath b/org.argeo.cms.pgsql/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/org.argeo.cms.pgsql/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.pgsql/.project b/org.argeo.cms.pgsql/.project deleted file mode 100644 index ff01ad98d..000000000 --- a/org.argeo.cms.pgsql/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.pgsql - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.cms.pgsql/bnd.bnd b/org.argeo.cms.pgsql/bnd.bnd deleted file mode 100644 index 9c7300926..000000000 --- a/org.argeo.cms.pgsql/bnd.bnd +++ /dev/null @@ -1 +0,0 @@ -Import-Package: org.postgresql;version="[42,43)" diff --git a/org.argeo.cms.pgsql/build.properties b/org.argeo.cms.pgsql/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/org.argeo.cms.pgsql/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java b/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java deleted file mode 100644 index 9db43df25..000000000 --- a/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.cms.pgsql.util; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import org.postgresql.Driver; - -/** Simple PostgreSQL check. */ -public class CheckPg { - - public List 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 result = new ArrayList<>(); - - Driver driver = new Driver(); - try (Connection conn = driver.connect(url, props); Statement s = conn.createStatement();) { - s.execute("SELECT * FROM pg_catalog.pg_tables"); - ResultSet rs = s.getResultSet(); - while (rs.next()) { - result.add(rs.getString("tablename")); - } - return result; - } catch (SQLException e) { - throw new IllegalStateException(e); - } - } - - public static void main(String[] args) { - new CheckPg().listTables().forEach(System.out::println); - } - -} diff --git a/org.argeo.cms.ux/.classpath b/org.argeo.cms.ux/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.ux/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.ux/.project b/org.argeo.cms.ux/.project new file mode 100644 index 000000000..a2f33e2c9 --- /dev/null +++ b/org.argeo.cms.ux/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.ux + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.ux/bnd.bnd b/org.argeo.cms.ux/bnd.bnd new file mode 100644 index 000000000..e69de29bb diff --git a/org.argeo.cms.ux/build.properties b/org.argeo.cms.ux/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/org.argeo.cms.ux/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java b/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java new file mode 100644 index 000000000..852cb5221 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java @@ -0,0 +1,64 @@ +package org.argeo.cms.media; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; + +public class SvgToPng { + + public void convertSvgDir(Path sourceDir, Path targetDir, int width) { + System.out.println("##\n## " + width + "px - " + sourceDir + "\n##"); + try { + if (targetDir == null) + targetDir = sourceDir.getParent().resolve(Integer.toString(width)); + Files.createDirectories(targetDir); + + PNGTranscoder transcoder = new PNGTranscoder(); + // transcoder.addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR, + // Color.WHITE); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width); + + for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) { + // FIXME extract base name + String baseName = null; // = FilenameUtils.getBaseName(source.toString()); + Path target = targetDir.resolve(baseName + ".png"); + convertSvgFile(transcoder, source, target); + } + } catch (IOException | TranscoderException e) { + throw new IllegalStateException("Cannot convert from " + sourceDir + " to " + targetDir, e); + } + + } + + protected void convertSvgFile(ImageTranscoder transcoder, Path source, Path target) + throws IOException, TranscoderException { + try (Reader reader = Files.newBufferedReader(source); OutputStream out = Files.newOutputStream(target);) { + TranscoderInput input = new TranscoderInput(reader); +// BufferedImage image = transcoder.createImage(32, 32); + TranscoderOutput output = new TranscoderOutput(out); + transcoder.transcode(input, output); + System.out.println(source.getFileName() + " -> " + target); + } + } + + public static void main(String[] args) throws Exception { + + Path path = Paths.get(args[0]); + + SvgToPng svgToPng = new SvgToPng(); + svgToPng.convertSvgDir(path, null, 16); + svgToPng.convertSvgDir(path, null, 32); + svgToPng.convertSvgDir(path, null, 64); + svgToPng.convertSvgDir(path, null, 96); + } +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java new file mode 100644 index 000000000..a1cd3d90e --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java @@ -0,0 +1,34 @@ +package org.argeo.cms.ux; + +import java.util.IdentityHashMap; + +import org.argeo.api.cms.ux.CmsEditable; +import org.argeo.api.cms.ux.CmsEditionEvent; +import org.argeo.api.cms.ux.CmsEditionListener; + +public abstract class AbstractCmsEditable implements CmsEditable { + private IdentityHashMap listeners = new IdentityHashMap<>(); + + protected void notifyListeners(CmsEditionEvent e) { + if (CmsEditionEvent.START_EDITING == e.getType()) { + for (CmsEditionListener listener : listeners.keySet()) + listener.editionStarted(e); + } else if (CmsEditionEvent.STOP_EDITING == e.getType()) { + for (CmsEditionListener listener : listeners.keySet()) + listener.editionStopped(e); + } else { + throw new IllegalArgumentException("Unkown edition event type " + e.getType()); + } + } + + @Override + public void addCmsEditionListener(CmsEditionListener listener) { + listeners.put(listener, new Object()); + } + + @Override + public void removeCmsEditionListener(CmsEditionListener listener) { + listeners.remove(listener, new Object()); + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java new file mode 100644 index 000000000..41c905ef6 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java @@ -0,0 +1,62 @@ +package org.argeo.cms.ux; + +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.api.cms.ux.CmsImageManager; + +/** Manages only public images so far. */ +public abstract class AbstractImageManager implements CmsImageManager { + public final static String NO_IMAGE = "icons/noPic-square-640px.png"; + public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320); + public final static Float NO_IMAGE_RATIO = 1f; + + protected Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) { + if (constraints.getWidth() != 0 && constraints.getHeight() != 0) { + return constraints; + } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) { + return orig; + } else if (constraints.getHeight() == 0) {// force width + return new Cms2DSize(constraints.getWidth(), + scale(orig.getHeight(), orig.getWidth(), constraints.getWidth())); + } else if (constraints.getWidth() == 0) {// force height + return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()), + constraints.getHeight()); + } + throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints); + } + + protected int scale(int origDimension, int otherDimension, int otherConstraint) { + return Math.round(origDimension * divide(otherConstraint, otherDimension)); + } + + protected float divide(int a, int b) { + return ((float) a) / ((float) b); + } + + /** @return null if not available */ + @Override + public String getImageTag(M node) { + return getImageTag(node, getImageSize(node)); + } + + protected String getImageTag(M node, Cms2DSize size) { + StringBuilder buf = getImageTagBuilder(node, size); + if (buf == null) + return null; + return buf.append("/>").toString(); + } + + /** @return null if not available */ + @Override + public StringBuilder getImageTagBuilder(M node, Cms2DSize size) { + return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); + } + + /** @return null if not available */ + protected StringBuilder getImageTagBuilder(M node, String width, String height) { + String url = getImageUrl(node); + if (url == null) + return null; + return CmsUxUtils.imgBuilder(url, width, height); + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java new file mode 100644 index 000000000..087b4ff7c --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java @@ -0,0 +1,36 @@ +package org.argeo.cms.ux; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentRepository; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.util.CurrentSubject; + +public class CmsUxUtils { + public static ContentSession getContentSession(ContentRepository contentRepository, CmsView cmsView) { + return CurrentSubject.callAs(cmsView.getCmsSession().getSubject(), () -> contentRepository.get()); + } + + public static String getTitle(Content content) { + return content.getName().getLocalPart(); + } + + /** singleton */ + private CmsUxUtils() { + + } + + public static StringBuilder imgBuilder(String src, String width, String height) { + return new StringBuilder(64).append("").toString(); + } + + public static String img(String src, Cms2DSize size) { + return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); + } +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java new file mode 100644 index 000000000..baaa25238 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java @@ -0,0 +1,29 @@ +package org.argeo.cms.ux.acr; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.argeo.api.acr.Content; +import org.argeo.cms.ux.widgets.AbstractHierarchicalPart; +import org.argeo.cms.ux.widgets.HierarchicalPart; + +public class ContentHierarchicalPart extends AbstractHierarchicalPart implements HierarchicalPart { + @Override + public List getChildren(Content content) { + List res = new ArrayList<>(); + if (isLeaf(content)) + return res; + if (content == null) + return res; + for (Iterator it = content.iterator(); it.hasNext();) { + res.add(it.next()); + } + + return res; + } + + protected boolean isLeaf(Content content) { + return false; + } +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java new file mode 100644 index 000000000..0a0ad0ea5 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java @@ -0,0 +1,14 @@ +package org.argeo.cms.ux.acr; + +import org.argeo.api.acr.Content; + +/** A part displaying or editing a content. */ +public interface ContentPart { + Content getContent(); + + @Deprecated + default Content getNode() { + return getContent(); + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractColumnsPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractColumnsPart.java new file mode 100644 index 000000000..143b9cc9e --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractColumnsPart.java @@ -0,0 +1,26 @@ +package org.argeo.cms.ux.widgets; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractColumnsPart extends AbstractDataPart implements ColumnsPart { + + private List> columns = new ArrayList<>(); + + @Override + public Column getColumn(int index) { + if (index >= columns.size()) + throw new IllegalArgumentException("There a only " + columns.size()); + return columns.get(index); + } + + @Override + public void addColumn(Column column) { + columns.add(column); + } + + @Override + public int getColumnCount() { + return columns.size(); + } +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractDataPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractDataPart.java new file mode 100644 index 000000000..04811af87 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractDataPart.java @@ -0,0 +1,65 @@ +package org.argeo.cms.ux.widgets; + +import java.util.IdentityHashMap; +import java.util.function.Consumer; + +public abstract class AbstractDataPart implements DataPart { + private Consumer onSelected; + private Consumer onAction; + + private IdentityHashMap, Object> views = new IdentityHashMap<>(); + + private INPUT data; + + @Override + public void setInput(INPUT data) { + this.data = data; + refresh(); + } + + @Override + public INPUT getInput() { + return data; + } + + @Override + public void onSelected(Consumer onSelected) { + this.onSelected = onSelected; + } + + @Override + public void onAction(Consumer onAction) { + this.onAction = onAction; + } + + public Consumer getOnSelected() { + return onSelected; + } + + public Consumer getOnAction() { + return onAction; + } + + @Override + public void refresh() { + for (DataView view : views.keySet()) { + view.refresh(); + } + } + + protected void notifyItemCountChange() { + for (DataView view : views.keySet()) { + view.notifyItemCountChange(); + } + } + + @Override + public void addView(DataView view) { + views.put(view, new Object()); + } + + @Override + public void removeView(DataView view) { + views.remove(view); + } +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java new file mode 100644 index 000000000..f1e89f1f4 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java @@ -0,0 +1,86 @@ +package org.argeo.cms.ux.widgets; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractGuidedForm implements GuidedForm { + private String formTitle; + private List pages = new ArrayList<>(); + private View view; + + @Override + public abstract void addPages(); + + public void addPage(AbstractGuidedFormPage page) { + page.setView(view); + pages.add(page); + } + + @Override + public boolean canFinish() { + return false; + } + + @Override + public boolean performFinish() { + return false; + } + + @Override + public boolean performCancel() { + return false; + } + + @Override + public int getPageCount() { + return pages.size(); + } + + @Override + public List getPages() { + return Collections.unmodifiableList(pages); + } + + @Override + public Page getStartingPage() { + if (pages.isEmpty()) + throw new IllegalStateException("No page available"); + return pages.get(0); + } + + @Override + public Page getPreviousPage(Page page) { + int index = pages.indexOf(page); + if (index == 0 || index == -1) { + // first page or page not found + return null; + } + return pages.get(index - 1); + } + + @Override + public Page getNextPage(Page page) { + int index = pages.indexOf(page); + if (index == pages.size() - 1 || index == -1) { + // last page or page not found + return null; + } + return pages.get(index + 1); + } + + public void setFormTitle(String formTitle) { + this.formTitle = formTitle; + } + + @Override + public String getFormTitle() { + return formTitle; + } + + @Override + public void setView(View view) { + this.view = view; + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java new file mode 100644 index 000000000..d56551008 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java @@ -0,0 +1,38 @@ +package org.argeo.cms.ux.widgets; + +import org.argeo.cms.ux.widgets.GuidedForm.View; +import org.argeo.cms.ux.widgets.GuidedForm.Page; + +public class AbstractGuidedFormPage implements Page { + private String pageName; + private String title; + private View view; + + public AbstractGuidedFormPage(String pageName) { + super(); + this.pageName = pageName; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setView(View container) { + this.view = container; + + } + + public String getPageName() { + return pageName; + } + + @Override + public String getTitle() { + return title; + } + + public View getView() { + return view; + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractHierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractHierarchicalPart.java new file mode 100644 index 000000000..ccdcf4ea5 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractHierarchicalPart.java @@ -0,0 +1,5 @@ +package org.argeo.cms.ux.widgets; + +public abstract class AbstractHierarchicalPart extends AbstractColumnsPart implements HierarchicalPart { + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractTabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractTabularPart.java new file mode 100644 index 000000000..835bc7ec7 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractTabularPart.java @@ -0,0 +1,6 @@ +package org.argeo.cms.ux.widgets; + +public abstract class AbstractTabularPart extends AbstractColumnsPart + implements TabularPart { + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/CmsDialog.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/CmsDialog.java new file mode 100644 index 000000000..3b1630d29 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/CmsDialog.java @@ -0,0 +1,10 @@ +package org.argeo.cms.ux.widgets; + +public interface CmsDialog { + + // must be the same value as org.eclipse.jface.window.Window#OK + int OK = 0; + // must be the same value as org.eclipse.jface.window.Window#CANCEL + int CANCEL = 1; + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/Column.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/Column.java new file mode 100644 index 000000000..71cd263f8 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/Column.java @@ -0,0 +1,18 @@ +package org.argeo.cms.ux.widgets; + +import org.argeo.api.cms.ux.CmsIcon; + +/** A column in a data representation. */ +@FunctionalInterface +public interface Column { + String getText(TYPE model); + + default int getWidth() { + return 200; + } + + default CmsIcon getIcon(TYPE model) { + return null; + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java new file mode 100644 index 000000000..2aaeb49de --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java @@ -0,0 +1,12 @@ +package org.argeo.cms.ux.widgets; + +/** A presentation of data in columns. */ +public interface ColumnsPart extends DataPart { + + Column getColumn(int index); + + void addColumn(Column column); + + int getColumnCount(); + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java new file mode 100644 index 000000000..9d3ca33ff --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java @@ -0,0 +1,28 @@ +package org.argeo.cms.ux.widgets; + +import java.util.function.Consumer; + +public interface DataPart { + void setInput(INPUT data); + + INPUT getInput(); + + void onSelected(Consumer onSelected); + + Consumer getOnSelected(); + + void onAction(Consumer onAction); + + Consumer getOnAction(); + + void refresh(); + + void addView(DataView view); + + void removeView(DataView view); + +// void select(TYPE data); +// +// TYPE getSelected(); + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataView.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataView.java new file mode 100644 index 000000000..311cf924e --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataView.java @@ -0,0 +1,7 @@ +package org.argeo.cms.ux.widgets; + +public interface DataView { + void refresh(); + + void notifyItemCountChange(); +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DefaultTabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DefaultTabularPart.java new file mode 100644 index 000000000..cb30af675 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DefaultTabularPart.java @@ -0,0 +1,46 @@ +package org.argeo.cms.ux.widgets; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultTabularPart extends AbstractTabularPart implements TabularPart { + private List content; + + @Override + public int getItemCount() { + return content.size(); + } + + @Override + public T getData(int row) { + assert row < getItemCount(); + return content.get(row); + } + + @Override + public void refresh() { + INPUT input = getInput(); + if (input == null) { + content = new ArrayList<>(); + return; + } + content = asList(input); + super.refresh(); + } + + protected List asList(INPUT input) { + List res = new ArrayList<>(); + content.clear(); + if (input instanceof List) { + content = (List) input; + } else if (input instanceof Iterable) { + for (T item : (Iterable) input) + content.add(item); + } else { + throw new IllegalArgumentException( + "Unsupported class " + input.getClass() + ", method should be overridden."); + } + return res; + } + +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/EditablePart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/EditablePart.java new file mode 100644 index 000000000..1257cfc92 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/EditablePart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ux.widgets; + +/** Manages whether an editable or non editable control is shown. */ +public interface EditablePart { + public void startEditing(); + + public void stopEditing(); +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java new file mode 100644 index 000000000..de8554e09 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java @@ -0,0 +1,45 @@ +package org.argeo.cms.ux.widgets; + +import java.util.List; + +public interface GuidedForm { + String getFormTitle(); + + boolean canFinish(); + + boolean performFinish(); + + boolean performCancel(); + + void addPages(); + + int getPageCount(); + + List getPages(); + + Page getStartingPage(); + + Page getPreviousPage(Page page); + + Page getNextPage(Page page); + + void setView(View view); + + interface Page { + + default boolean canFlipToNextPage() { + return true; + } + + default String getMessage() { + return null; + } + + String getTitle(); + + } + + interface View { + void updateButtons(); + } +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java new file mode 100644 index 000000000..8f0e79845 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ux.widgets; + +import java.util.List; + +/** A hierarchical representation of data. */ +public interface HierarchicalPart extends ColumnsPart { + List getChildren(T parent); +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java new file mode 100644 index 000000000..01b4d6b6b --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ux.widgets; + +/** A tabular presentation of data. */ +public interface TabularPart extends ColumnsPart { + int getItemCount(); + + TYPE getData(int row); +} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TreeParent.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TreeParent.java new file mode 100644 index 000000000..97dedcc24 --- /dev/null +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TreeParent.java @@ -0,0 +1,133 @@ +package org.argeo.cms.ux.widgets; + +import java.util.ArrayList; +import java.util.List; + +/** Parent / children semantic to be used for simple UI Tree structure */ +public class TreeParent { + private String name; + private TreeParent parent; + + private List 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(); + } + + 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, super.dispose() 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 List getChildrenOfType(Class clss) { + List lst = new ArrayList(); + for (Object obj : children) { + if (clss.isAssignableFrom(obj.getClass())) + lst.add((T) obj); + } + return lst; + } + + public synchronized boolean hasChildren() { + return children.size() > 0; + } + + public Object getChildByName(String name) { + for (Object child : children) { + if (child.toString().equals(name)) + return child; + } + return null; + } + + public synchronized Boolean isLoaded() { + return loaded; + } + + public String getName() { + return name; + } + + public void setParent(TreeParent parent) { + this.parent = parent; + if (parent != null && parent.path != null) + this.path = parent.path + '/' + name; + else + this.path = '/' + name; + } + + public TreeParent getParent() { + return parent; + } + + public String toString() { + return getName(); + } + + public int compareTo(TreeParent o) { + return name.compareTo(o.name); + } + + @Override + public int hashCode() { + if (path != null) + return path.hashCode(); + else + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (path != null && obj instanceof TreeParent) + return path.equals(((TreeParent) obj).path); + else + return name.equals(obj.toString()); + } + +} diff --git a/org.argeo.cms/.classpath b/org.argeo.cms/.classpath index 4a00becd8..3628e3368 100644 --- a/org.argeo.cms/.classpath +++ b/org.argeo.cms/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms/.gitignore b/org.argeo.cms/.gitignore new file mode 100644 index 000000000..77dacfc82 --- /dev/null +++ b/org.argeo.cms/.gitignore @@ -0,0 +1 @@ +node/ \ No newline at end of file diff --git a/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml new file mode 100644 index 000000000..afd4e0aaf --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsAuthenticator.xml b/org.argeo.cms/OSGI-INF/cmsAuthenticator.xml new file mode 100644 index 000000000..9b152147e --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsAuthenticator.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsContentRepository.xml b/org.argeo.cms/OSGI-INF/cmsContentRepository.xml new file mode 100644 index 000000000..306717b1c --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsContentRepository.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsContext.xml b/org.argeo.cms/OSGI-INF/cmsContext.xml index 63d43192e..d2413bd1f 100644 --- a/org.argeo.cms/OSGI-INF/cmsContext.xml +++ b/org.argeo.cms/OSGI-INF/cmsContext.xml @@ -6,5 +6,7 @@ + - + + diff --git a/org.argeo.cms/OSGI-INF/cmsDeployment.xml b/org.argeo.cms/OSGI-INF/cmsDeployment.xml index d36a91110..66541827d 100644 --- a/org.argeo.cms/OSGI-INF/cmsDeployment.xml +++ b/org.argeo.cms/OSGI-INF/cmsDeployment.xml @@ -1,8 +1,10 @@ - - + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsEventBus.xml b/org.argeo.cms/OSGI-INF/cmsEventBus.xml new file mode 100644 index 000000000..6bb67ceed --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsEventBus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsOsgiLogger.xml b/org.argeo.cms/OSGI-INF/cmsOsgiLogger.xml new file mode 100644 index 000000000..93fc5c0a4 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsOsgiLogger.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsState.xml b/org.argeo.cms/OSGI-INF/cmsState.xml index a81e9f068..71dc6d4db 100644 --- a/org.argeo.cms/OSGI-INF/cmsState.xml +++ b/org.argeo.cms/OSGI-INF/cmsState.xml @@ -4,4 +4,5 @@ + diff --git a/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml b/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml new file mode 100644 index 000000000..52c75318e --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/cmsUserManager.xml b/org.argeo.cms/OSGI-INF/cmsUserManager.xml index 524c054ec..d76c89a1b 100644 --- a/org.argeo.cms/OSGI-INF/cmsUserManager.xml +++ b/org.argeo.cms/OSGI-INF/cmsUserManager.xml @@ -1,10 +1,9 @@ - - + + - + - - + diff --git a/org.argeo.cms/OSGI-INF/deployConfig.xml b/org.argeo.cms/OSGI-INF/deployConfig.xml deleted file mode 100644 index 03094342e..000000000 --- a/org.argeo.cms/OSGI-INF/deployConfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml deleted file mode 100644 index eb048d9f5..000000000 --- a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml b/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml deleted file mode 100644 index c331aa430..000000000 --- a/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.cms/OSGI-INF/transactionManager.xml b/org.argeo.cms/OSGI-INF/transactionManager.xml new file mode 100644 index 000000000..df317e937 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/transactionManager.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms/OSGI-INF/uuidFactory.xml b/org.argeo.cms/OSGI-INF/uuidFactory.xml new file mode 100644 index 000000000..c1ad6f8a9 --- /dev/null +++ b/org.argeo.cms/OSGI-INF/uuidFactory.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index e75adcdc3..ade2f3aa9 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -1,18 +1,19 @@ Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator Import-Package: \ -org.argeo.osgi.transaction, \ -org.apache.commons.httpclient.cookie;resolution:=optional,\ -!com.sun.security.jgss,\ org.osgi.*;version=0.0.0,\ * Service-Component:\ +OSGI-INF/cmsOsgiLogger.xml,\ +OSGI-INF/uuidFactory.xml,\ +OSGI-INF/cmsEventBus.xml,\ OSGI-INF/cmsState.xml,\ -OSGI-INF/simpleTransactionManager.xml,\ -OSGI-INF/nodeUserAdmin.xml,\ +OSGI-INF/transactionManager.xml,\ +OSGI-INF/cmsUserAdmin.xml,\ OSGI-INF/cmsUserManager.xml,\ -OSGI-INF/deployConfig.xml,\ +OSGI-INF/cmsContentRepository.xml,\ +OSGI-INF/cmsAcrHttpHandler.xml,\ OSGI-INF/cmsDeployment.xml,\ OSGI-INF/cmsContext.xml,\ diff --git a/org.argeo.cms/build.properties b/org.argeo.cms/build.properties index db86d95a5..6ca041a2a 100644 --- a/org.argeo.cms/build.properties +++ b/org.argeo.cms/build.properties @@ -1,12 +1,7 @@ -output.. = bin/ bin.includes = META-INF/,\ .,\ bin/,\ OSGI-INF/,\ - OSGI-INF/simpleTransactionManager.xml,\ - OSGI-INF/cmsState.xml,\ - OSGI-INF/nodeUserAdmin.xml,\ - OSGI-INF/deployConfig.xml,\ - OSGI-INF/cmsDeployment.xml,\ - OSGI-INF/cmsContext.xml + OSGI-INF/cmsEventBus.xml source.. = src/ +output.. = bin/ diff --git a/org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java b/org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java index a7049a4f4..cefdb86b3 100644 --- a/org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java +++ b/org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java @@ -8,15 +8,21 @@ import java.util.Map; import org.argeo.api.cms.CmsApp; import org.argeo.api.cms.CmsAppListener; -import org.argeo.api.cms.CmsTheme; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.ux.CmsTheme; /** Base class for {@link CmsApp}s. */ public abstract class AbstractCmsApp implements CmsApp { + private CmsContext cmsContext; + private Map themes = Collections.synchronizedMap(new HashMap<>()); private List cmsAppListeners = new ArrayList<>(); - protected abstract String getThemeId(String uiName); + /** To be overridden in order to provide themes. */ + protected String getThemeId(String uiName) { + return null; + } @Override public CmsTheme getTheme(String uiName) { @@ -35,7 +41,7 @@ public abstract class AbstractCmsApp implements CmsApp { String themeId = getThemeId(uiName); if ("org.eclipse.rap.rwt.theme.Default".equals(themeId)) continue uiNames; - if (!themes.containsKey(themeId)) { + if (themeId != null && !themes.containsKey(themeId)) { themeMissing = true; break uiNames; } @@ -66,4 +72,15 @@ public abstract class AbstractCmsApp implements CmsApp { cmsAppListeners.remove(listener); } + @Override + public CmsContext getCmsContext() { + return cmsContext; + } + + public void setCmsContext(CmsContext cmsContext) { + this.cmsContext = cmsContext; + } + + + } diff --git a/org.argeo.cms/src/org/argeo/cms/AbstractKeyring.java b/org.argeo.cms/src/org/argeo/cms/AbstractKeyring.java new file mode 100644 index 000000000..c2c9c6107 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/AbstractKeyring.java @@ -0,0 +1,290 @@ +package org.argeo.cms; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; +import java.util.Iterator; + +import javax.crypto.SecretKey; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.keyring.PBEKeySpecCallback; +import org.argeo.cms.util.CurrentSubject; +import org.argeo.cms.util.StreamUtils; +import org.argeo.api.cms.keyring.CryptoKeyring; +import org.argeo.api.cms.keyring.Keyring; + +/** username / password based keyring. TODO internationalize */ +public abstract class AbstractKeyring implements Keyring, CryptoKeyring { + // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING"; + + // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT; + private CallbackHandler defaultCallbackHandler; + + private String charset = "UTF-8"; + + /** + * Default provider is bouncy castle, in order to have consistent behaviour + * across implementations + */ + private String securityProviderName = "BC"; + + /** + * Whether the keyring has already been created in the past with a master + * password + */ + protected abstract Boolean isSetup(); + + /** + * Setup the keyring persistently, {@link #isSetup()} must return true + * afterwards + */ + protected abstract void setup(char[] password); + + /** Populates the key spec callback */ + protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback); + + protected abstract void encrypt(String path, InputStream unencrypted); + + protected abstract InputStream decrypt(String path); + + /** Triggers lazy initialization */ + protected SecretKey getSecretKey(char[] password) { + Subject subject = CurrentSubject.current(); + if (subject == null) + throw new IllegalStateException("Current subject cannot be null"); + // we assume only one secrete key is available + Iterator iterator = subject.getPrivateCredentials(SecretKey.class).iterator(); + if (!iterator.hasNext() || password != null) {// not initialized + CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler() + : new PasswordProvidedCallBackHandler(password); + ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + try { + LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject, callbackHandler); + loginContext.login(); + // FIXME will login even if password is wrong + iterator = subject.getPrivateCredentials(SecretKey.class).iterator(); + return iterator.next(); + } catch (LoginException e) { + throw new IllegalStateException("Keyring login failed", e); + } finally { + Thread.currentThread().setContextClassLoader(currentContextClassLoader); + } + + } else { + SecretKey secretKey = iterator.next(); + if (iterator.hasNext()) + throw new IllegalStateException("More than one secret key in private credentials"); + return secretKey; + } + } + + public InputStream getAsStream(String path) { + return decrypt(path); + } + + public void set(String path, InputStream in) { + encrypt(path, in); + } + + public char[] getAsChars(String path) { + // InputStream in = getAsStream(path); + // CharArrayWriter writer = null; + // Reader reader = null; + try (InputStream in = getAsStream(path); + CharArrayWriter writer = new CharArrayWriter(); + Reader reader = new InputStreamReader(in, charset);) { + StreamUtils.copy(reader, writer); + return writer.toCharArray(); + } catch (IOException e) { + throw new IllegalStateException("Cannot decrypt to char array", e); + } finally { + // IOUtils.closeQuietly(reader); + // IOUtils.closeQuietly(in); + // IOUtils.closeQuietly(writer); + } + } + + public void set(String path, char[] arr) { + // ByteArrayOutputStream out = new ByteArrayOutputStream(); + // ByteArrayInputStream in = null; + // Writer writer = null; + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + Writer writer = new OutputStreamWriter(out, charset);) { + // writer = new OutputStreamWriter(out, charset); + writer.write(arr); + writer.flush(); + // in = new ByteArrayInputStream(out.toByteArray()); + try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) { + set(path, in); + } + } catch (IOException e) { + throw new IllegalStateException("Cannot encrypt to char array", e); + } finally { + // IOUtils.closeQuietly(writer); + // IOUtils.closeQuietly(out); + // IOUtils.closeQuietly(in); + } + } + + public void unlock(char[] password) { + if (!isSetup()) + setup(password); + SecretKey secretKey = getSecretKey(password); + if (secretKey == null) + throw new IllegalStateException("Could not unlock keyring"); + } + + protected Provider getSecurityProvider() { + return Security.getProvider(securityProviderName); + } + + public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) { + this.defaultCallbackHandler = defaultCallbackHandler; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public void setSecurityProviderName(String securityProviderName) { + this.securityProviderName = securityProviderName; + } + + // @Deprecated + // protected static byte[] hash(char[] password, byte[] salt, Integer + // iterationCount) { + // ByteArrayOutputStream out = null; + // OutputStreamWriter writer = null; + // try { + // out = new ByteArrayOutputStream(); + // writer = new OutputStreamWriter(out, "UTF-8"); + // writer.write(password); + // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256"); + // pwDigest.reset(); + // pwDigest.update(salt); + // byte[] btPass = pwDigest.digest(out.toByteArray()); + // for (int i = 0; i < iterationCount; i++) { + // pwDigest.reset(); + // btPass = pwDigest.digest(btPass); + // } + // return btPass; + // } catch (Exception e) { + // throw new CmsException("Cannot hash", e); + // } finally { + // IOUtils.closeQuietly(out); + // IOUtils.closeQuietly(writer); + // } + // + // } + + /** + * Convenience method using the underlying callback to ask for a password + * (typically used when the password is not saved in the keyring) + */ + protected char[] ask() { + PasswordCallback passwordCb = new PasswordCallback("Password", false); + Callback[] dialogCbs = new Callback[] { passwordCb }; + try { + defaultCallbackHandler.handle(dialogCbs); + char[] password = passwordCb.getPassword(); + return password; + } catch (Exception e) { + throw new IllegalStateException("Cannot ask for a password", e); + } + + } + + class KeyringCallbackHandler implements CallbackHandler { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + // checks + if (callbacks.length != 2) + throw new IllegalArgumentException( + "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}"); + if (!(callbacks[0] instanceof PasswordCallback)) + throw new UnsupportedCallbackException(callbacks[0]); + if (!(callbacks[1] instanceof PBEKeySpecCallback)) + throw new UnsupportedCallbackException(callbacks[0]); + + PasswordCallback passwordCb = (PasswordCallback) callbacks[0]; + PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1]; + + if (isSetup()) { + Callback[] dialogCbs = new Callback[] { passwordCb }; + defaultCallbackHandler.handle(dialogCbs); + } else {// setup keyring + TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION, + "Enter a master password which will protect your private data"); + TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION, + "(for example your credentials to third-party services)"); + TextOutputCallback textCb3 = new TextOutputCallback(TextOutputCallback.INFORMATION, + "Don't forget this password since the data cannot be read without it"); + PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false); + // first try + Callback[] dialogCbs = new Callback[] { textCb1, textCb2, textCb3, passwordCb, confirmPasswordCb }; + defaultCallbackHandler.handle(dialogCbs); + + // if passwords different, retry (except if cancelled) + while (passwordCb.getPassword() != null + && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) { + TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR, + "The passwords do not match"); + dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb }; + defaultCallbackHandler.handle(dialogCbs); + } + + if (passwordCb.getPassword() != null) {// not cancelled + setup(passwordCb.getPassword()); + } + } + + if (passwordCb.getPassword() != null) + handleKeySpecCallback(pbeCb); + } + + } + + class PasswordProvidedCallBackHandler implements CallbackHandler { + private final char[] password; + + public PasswordProvidedCallBackHandler(char[] password) { + this.password = password; + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + // checks + if (callbacks.length != 2) + throw new IllegalArgumentException( + "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}"); + if (!(callbacks[0] instanceof PasswordCallback)) + throw new UnsupportedCallbackException(callbacks[0]); + if (!(callbacks[1] instanceof PBEKeySpecCallback)) + throw new UnsupportedCallbackException(callbacks[0]); + + PasswordCallback passwordCb = (PasswordCallback) callbacks[0]; + passwordCb.setPassword(password); + PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1]; + handleKeySpecCallback(pbeCb); + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java deleted file mode 100644 index a01858aa9..000000000 --- a/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.argeo.cms; - -/** Framework agnostic interface for log notifications */ -@Deprecated -public interface ArgeoLogListener { - /** - * Appends a log - * - * @param username - * authentified user, null for anonymous - * @param level - * INFO, DEBUG, WARN, etc. (logging framework specific) - * @param category - * hierarchy (logging framework specific) - * @param thread - * name of the thread which logged this message - * @param msg - * any object as long as its toString() method returns the - * message - * @param exception - * exception in log4j ThrowableStrRep format - */ - public void appendLog(String username, Long timestamp, String level, - String category, String thread, Object msg, String[] exception); -} diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java deleted file mode 100644 index 71c503900..000000000 --- a/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms; - -/** - * Logging framework agnostic identifying a logging service, to which one can - * register - */ -@Deprecated -public interface ArgeoLogger { - /** - * Register for events by threads with the same authentication (or all - * threads if admin) - */ - public void register(ArgeoLogListener listener, - Integer numberOfPreviousEvents); - - /** - * For admin use only: register for all users - * - * @param listener - * the log listener - * @param numberOfPreviousEvents - * the number of previous events to notify - * @param everything - * if true even anonymous is logged - */ - public void registerForAll(ArgeoLogListener listener, - Integer numberOfPreviousEvents, boolean everything); - - public void unregister(ArgeoLogListener listener); - - public void unregisterForAll(ArgeoLogListener listener); -} diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoNames.java b/org.argeo.cms/src/org/argeo/cms/ArgeoNames.java index 90260c314..4e869b7fd 100644 --- a/org.argeo.cms/src/org/argeo/cms/ArgeoNames.java +++ b/org.argeo.cms/src/org/argeo/cms/ArgeoNames.java @@ -1,6 +1,7 @@ package org.argeo.cms; /** JCR names in the http://www.argeo.org/argeo namespace */ +@Deprecated public interface ArgeoNames { public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo"; diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoTypes.java b/org.argeo.cms/src/org/argeo/cms/ArgeoTypes.java index ce0439102..9995725d8 100644 --- a/org.argeo.cms/src/org/argeo/cms/ArgeoTypes.java +++ b/org.argeo.cms/src/org/argeo/cms/ArgeoTypes.java @@ -1,9 +1,10 @@ package org.argeo.cms; /** JCR types in the http://www.argeo.org/argeo namespace */ +@Deprecated public interface ArgeoTypes { public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository"; - + // tabular public final static String ARGEO_TABLE = "argeo:table"; public final static String ARGEO_COLUMN = "argeo:column"; diff --git a/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java b/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java new file mode 100644 index 000000000..43343bfc7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java @@ -0,0 +1,162 @@ +package org.argeo.cms; + +import java.util.Objects; + +/** A property that can be used to configure a CMS node deployment. */ +public enum CmsDeployProperty { + // + // DIRECTORY + // + DIRECTORY("argeo.directory", 64), + // + // DATABASE + // + /** URL of the database backend. */ + DB_URL("argeo.db.url"), + /** DB user of the database backend. */ + DB_USER("argeo.db.user"), + /** DB user password of the database backend. */ + DB_PASSWORD("argeo.db.password"), + // + // NETWORK + // + /** Either a host or an IP address. Restricts all servers to it. */ + HOST("argeo.host"), + /** Either a host or an IP address. Restricts all servers to it. */ + DNS("argeo.dns", 16), + // + // HTTP + // + /** Request an HTTP server on this port. */ + HTTP_PORT("argeo.http.port"), + /** Request an HTTPS server on this port. */ + HTTPS_PORT("argeo.https.port"), + /** + * The HTTP header used to convey the DN of a client verified by a reverse + * proxy. Typically SSL_CLIENT_S_DN for Apache. + */ + HTTP_PROXY_SSL_HEADER_DN("argeo.http.proxy.ssl.header.dn"), + // + // SSL + // + /** SSL keystore for the system. */ + SSL_KEYSTORE("argeo.ssl.keystore"), + /** SSL keystore password for the system. */ + SSL_PASSWORD("argeo.ssl.password"), + /** SSL keystore type password for the system. */ + SSL_KEYSTORETYPE("argeo.ssl.keystoretype"), + /** SSL password for the private key. */ + SSL_KEYPASSWORD("argeo.ssl.keypassword"), + /** Whether a client certificate is required. */ + SSL_NEEDCLIENTAUTH("argeo.ssl.needclientauth"), + /** Whether a client certificate can be used. */ + SSL_WANTCLIENTAUTH("argeo.ssl.wantclientauth"), + /** SSL protocol to use. */ + SSL_PROTOCOL("argeo.ssl.protocol"), + /** SSL algorithm to use. */ + SSL_ALGORITHM("argeo.ssl.algorithm"), + /** Custom SSL trust store. */ + SSL_TRUSTSTORE("argeo.ssl.truststore"), + /** Custom SSL trust store type. */ + SSL_TRUSTSTORETYPE("argeo.ssl.truststoretype"), + /** Custom SSL trust store type. */ + SSL_TRUSTSTOREPASSWORD("argeo.ssl.truststorepassword"), + // + // WEBSOCKET + // + /** Whether web socket should be enables in web server. */ + WEBSOCKET_ENABLED("argeo.websocket.enabled"), + // + // SSH + // + /** Request an HTTP server on this port. */ + SSHD_PORT("argeo.sshd.port"), + /** Path to admin authorized keys file. */ + SSHD_AUTHORIZEDKEYS("argeo.sshd.authorizedkeys"), + // + // INTERNATIONALIZATION + // + /** Locales enabled for this system, the first one is considered the default. */ + LOCALE("argeo.locale", 256), + // + // NODE + // + /** Directories to copy to the data area during the first initialisation. */ + NODE_INIT("argeo.node.init", 64), + // + // JAVA + // + /** Custom JAAS config. */ + JAVA_LOGIN_CONFIG("java.security.auth.login.config", true), + // + // OSGi + // + /** OSGi writable data area. */ + OSGI_INSTANCE_AREA("osgi.instance.area"), + /** OSGi writable configuration area. */ + OSGI_CONFIGURATION_AREA("osgi.configuration.area"), + // + ; + + private String property; + private boolean systemPropertyOnly = false; + + private int maxCount = 1; + + CmsDeployProperty(String property) { + this(property, 1, false); + } + + CmsDeployProperty(String property, int maxCount) { + this(property, maxCount, false); + } + + CmsDeployProperty(String property, boolean systemPropertyOnly) { + this.property = property; + } + + CmsDeployProperty(String property, int maxCount, boolean systemPropertyOnly) { + this.property = property; + this.systemPropertyOnly = systemPropertyOnly; + this.maxCount = maxCount; + } + + public String getProperty() { + return property; + } + + public boolean isSystemPropertyOnly() { + return systemPropertyOnly; + } + + public int getMaxCount() { + return maxCount; + } + + public static CmsDeployProperty find(String property) { + int index = getPropertyIndex(property); + String propertyName = index == 0 ? property : property.substring(0, property.lastIndexOf('.')); + for (CmsDeployProperty deployProperty : values()) { + if (deployProperty.getProperty().equals(propertyName)) + return deployProperty; + } + return null; + } + + public static int getPropertyIndex(String property) { + Objects.requireNonNull(property); + int lastDot = property.lastIndexOf('.'); + if (lastDot <= 0 || lastDot == (property.length() - 1)) { + throw new IllegalArgumentException("Property " + property + " is not qualified (must contain a dot)."); + } + String lastSegment = property.substring(lastDot + 1); + int index; + try { + index = Integer.parseInt(lastSegment); + } catch (NumberFormatException e) { + index = 0; + } + return index; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/CmsException.java b/org.argeo.cms/src/org/argeo/cms/CmsException.java deleted file mode 100644 index 09d55c227..000000000 --- a/org.argeo.cms/src/org/argeo/cms/CmsException.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.argeo.cms; - -/** @deprecated Use standard Java {@link RuntimeException} instead. */ -@Deprecated -public class CmsException extends RuntimeException { - private static final long serialVersionUID = -5341764743356771313L; - - public CmsException(String message) { - super(message); - } - - public CmsException(String message, Throwable e) { - super(message, e); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/CmsSshd.java b/org.argeo.cms/src/org/argeo/cms/CmsSshd.java new file mode 100644 index 000000000..41968be7e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/CmsSshd.java @@ -0,0 +1,9 @@ +package org.argeo.cms; + +import org.argeo.api.cms.CmsConstants; + +/** Just a marker interface for the time being. */ +public interface CmsSshd { + final static String NODE_USERNAME_ALIAS = "user.name"; + final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser"; +} diff --git a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java deleted file mode 100644 index cd76d65ef..000000000 --- a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.argeo.cms; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Set; - -import javax.security.auth.Subject; - -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** - * Provide method interfaces to manage user concepts without accessing directly - * the userAdmin. - */ -public interface CmsUserManager { - - // CurrentUser - /** Returns the e-mail of the current logged in user */ - public String getMyMail(); - - // Other users - /** Returns a {@link User} given a username */ - public User getUser(String username); - - /** Can be a group or a user */ - public String getUserDisplayName(String dn); - - /** Can be a group or a user */ - public String getUserMail(String dn); - - /** Lists all roles of the given user */ - public String[] getUserRoles(String dn); - - /** Checks if the passed user belongs to the passed role */ - public boolean isUserInRole(String userDn, String roleDn); - - // Search - /** Returns a filtered list of roles */ - public Role[] getRoles(String filter) throws InvalidSyntaxException; - - /** Recursively lists users in a given group. */ - public Set listUsersInGroup(String groupDn, String filter); - - /** Search among groups including system roles and users if needed */ - public List listGroups(String filter, boolean includeUsers, boolean includeSystemRoles); - - /* MISCELLANEOUS */ - /** Returns the dn of a role given its local ID */ - public String buildDefaultDN(String localId, int type); - - /** Exposes the main default domain name for this instance */ - public String getDefaultDomainName(); - - /** - * Search for a {@link User} (might also be a group) whose uid or cn is equals - * to localId within the various user repositories defined in the current - * context. - */ - public User getUserFromLocalId(String localId); - - void changeOwnPassword(char[] oldPassword, char[] newPassword); - - void resetPassword(String username, char[] newPassword); - - @Deprecated - String addSharedSecret(String username, int hours); - -// String addSharedSecret(String username, String authInfo, String authToken); - - void addAuthToken(String userDn, String token, Integer hours, String... roles); - - void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles); - - void expireAuthToken(String token); - - void expireAuthTokens(Subject subject); - -// User createUserFromPerson(Node person); - -// @Deprecated -// public UserAdmin getUserAdmin(); -// -// @Deprecated -// public UserTransaction getUserTransaction(); -} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/CurrentUser.java new file mode 100644 index 000000000..ad12a8665 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/CurrentUser.java @@ -0,0 +1,187 @@ +package org.argeo.cms; + +import java.security.Principal; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.CmsSessionId; +import org.argeo.cms.internal.auth.CmsSessionImpl; +import org.argeo.cms.internal.auth.ImpliedByPrincipal; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.util.CurrentSubject; +import org.osgi.service.useradmin.Authorization; + +/** + * Programmatic access to the currently authenticated user, within a CMS + * context. + */ +public final class CurrentUser { + /** + * Technical username of the currently authenticated user. + * + * @return the authenticated username or null if not authenticated / anonymous + */ + public static String getUsername() { + return getUsername(currentSubject()); + } + + /** + * Human readable name of the currently authenticated user (typically first name + * and last name). + */ + public static String getDisplayName() { + return getDisplayName(currentSubject()); + } + + /** Whether a user is currently authenticated. */ + public static boolean isAnonymous() { + return isAnonymous(currentSubject()); + } + + /** Locale of the current user */ + public static Locale locale() { + return locale(currentSubject()); + } + + /** Roles of the currently logged-in user */ + public static Set roles() { + return roles(currentSubject()); + } + + /** Returns true if the current user is in the specified role */ + public static boolean isInRole(String role) { + Set roles = roles(); + return roles.contains(role); + } + + /** Implies this {@link SystemRole} in this context. */ + public static boolean implies(SystemRole role, String context) { + return role.implied(currentSubject(), context); + } + + /** Implies this role name, also independently of the context. */ + public static boolean implies(String role, String context) { + return SystemRole.implied(NamespaceUtils.parsePrefixedName(role), currentSubject(), context); + } + + /** Get the primary context this user belongs to. */ + public static boolean isUserContext(String context) { + // TODO have the role context as a separated credential in the Subjecto? + return RoleNameUtils.getContext(getUsername()).equalsIgnoreCase(context); + } + + /** Executes as the current user */ + public static T doAs(PrivilegedAction action) { + return Subject.doAs(currentSubject(), action); + } + + /** Executes as the current user */ + public static T tryAs(PrivilegedExceptionAction action) throws PrivilegedActionException { + return Subject.doAs(currentSubject(), action); + } + + /* + * WRAPPERS + */ + + public static String getUsername(Subject subject) { + if (subject == null) + throw new IllegalArgumentException("Subject cannot be null"); + if (subject.getPrincipals(X500Principal.class).size() != 1) + return CmsConstants.ROLE_ANONYMOUS; + Principal principal = subject.getPrincipals(X500Principal.class).iterator().next(); + return principal.getName(); + } + + public static String getDisplayName(Subject subject) { + return getAuthorization(subject).toString(); + } + + public static Set roles(Subject subject) { + Set roles = new HashSet(); + roles.add(getUsername(subject)); + for (Principal group : subject.getPrincipals(ImpliedByPrincipal.class)) { + roles.add(group.getName()); + } + return roles; + } + + public static Locale locale(Subject subject) { + Set locales = subject.getPublicCredentials(Locale.class); + if (locales.isEmpty()) { + Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale(); + return defaultLocale; + } else + return locales.iterator().next(); + } + + /** Whether this user is currently authenticated. */ + public static boolean isAnonymous(Subject subject) { + if (subject == null) + return true; + String username = getUsername(subject); + return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS); + } + + public static CmsSession getCmsSession() { + Subject subject = currentSubject(); + Iterator it = subject.getPrivateCredentials(CmsSessionId.class).iterator(); + if (!it.hasNext()) + throw new IllegalStateException("No CMS session id available for " + subject); + CmsSessionId cmsSessionId = it.next(); + if (it.hasNext()) + throw new IllegalStateException("More than one CMS session id available for " + subject); + return CmsContextImpl.getCmsContext().getCmsSessionByUuid(cmsSessionId.getUuid()); + } + + public static boolean isAvailable() { + return CurrentSubject.current() != null; + } + + /* + * HELPERS + */ + private static Subject currentSubject() { + Subject subject = CurrentSubject.current(); + if (subject == null) + throw new IllegalStateException("Cannot find related subject"); + return subject; + } + + private static Authorization getAuthorization(Subject subject) { + return subject.getPrivateCredentials(Authorization.class).iterator().next(); + } + + public static boolean logoutCmsSession(Subject subject) { + UUID nodeSessionId; + if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1) + nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid(); + else + return false; + CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByUuid(nodeSessionId); + + // FIXME logout all views + // TODO check why it is sometimes null + if (cmsSession != null) + cmsSession.close(); + // if (log.isDebugEnabled()) + // log.debug("Logged out CMS session " + cmsSession.getUuid()); + return true; + } + + /** singleton */ + private CurrentUser() { + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java index f02e6a2b4..8aca8768a 100644 --- a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java @@ -1,15 +1,9 @@ package org.argeo.cms; -import java.security.AccessController; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import java.util.ResourceBundle; -import javax.security.auth.Subject; - import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.CurrentUser; /** Utilities simplifying the development of localization enums. */ public class LocaleUtils { @@ -64,9 +58,10 @@ public class LocaleUtils { /** Where the search for a message is actually performed. */ public static String local(String key, Locale locale, String resource, ClassLoader classLoader) { ResourceBundle rb = ResourceBundle.getBundle(resource, locale, classLoader); - assert key.length() > 2; - if (isLocaleKey(key)) + if (isLocaleKey(key)) { + assert key.length() > 1; key = key.substring(1); + } if (rb.containsKey(key)) return rb.getString(key); else // for simple cases, the key will actually be the English word @@ -102,7 +97,7 @@ public class LocaleUtils { static Locale getCurrentLocale() { Locale currentLocale = null; - if (Subject.getSubject(AccessController.getContext()) != null) + if (CurrentUser.isAvailable()) currentLocale = CurrentUser.locale(); else if (threadLocale.get() != null) { currentLocale = threadLocale.get(); @@ -118,26 +113,4 @@ public class LocaleUtils { // return Locale.getDefault(); } - /** Returns null if argument is null. */ - public static List asLocaleList(Object locales) { - if (locales == null) - return null; - ArrayList availableLocales = new ArrayList(); - String[] codes = locales.toString().split(","); - for (int i = 0; i < codes.length; i++) { - String code = codes[i]; - // variant not supported - int indexUnd = code.indexOf("_"); - Locale locale; - if (indexUnd > 0) { - String language = code.substring(0, indexUnd); - String country = code.substring(indexUnd + 1); - locale = new Locale(language, country); - } else { - locale = new Locale(code); - } - availableLocales.add(locale); - } - return availableLocales; - } } diff --git a/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java b/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java new file mode 100644 index 000000000..04302c42f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java @@ -0,0 +1,41 @@ +package org.argeo.cms; + +import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.cms.directory.ldap.LdapNameUtils; + +/** Simplifies analysis of system roles. */ +public class RoleNameUtils { + public static String getLastRdnValue(String dn) { + return LdapNameUtils.getLastRdnValue(dn); +// // we don't use LdapName for portability with Android +// // TODO make it more robust +// String[] parts = dn.split(","); +// String[] rdn = parts[0].split("="); +// return rdn[1]; + } + + public static QName getLastRdnAsName(String dn) { + String cn = getLastRdnValue(dn); + QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn); + return roleName; + } + + public static boolean isSystemRole(QName roleName) { + return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI); + } + + public static String getParent(String dn) { + int index = dn.indexOf(','); + return dn.substring(index + 1); + } + + /** Up two levels. */ + public static String getContext(String dn) { + return getParent(getParent(dn)); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/SystemRole.java b/org.argeo.cms/src/org/argeo/cms/SystemRole.java new file mode 100644 index 000000000..95643998a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/SystemRole.java @@ -0,0 +1,48 @@ +package org.argeo.cms; + +import java.util.Set; + +import javax.security.auth.Subject; +import javax.xml.namespace.QName; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.internal.auth.ImpliedByPrincipal; + +/** A programmatic role. */ +public interface SystemRole { + QName qName(); + + /** Whether this role is implied for this authenticated user. */ + default boolean implied(Subject subject, String context) { + return implied(qName(), subject, context); + } + + /** Whether this role is implied for this distinguished name. */ + default boolean implied(String dn, String context) { + String roleContext = RoleNameUtils.getContext(dn); + QName roleName = RoleNameUtils.getLastRdnAsName(dn); + return roleContext.equalsIgnoreCase(context) && qName().equals(roleName); + } + + /** + * Whether this role is implied for this authenticated subject. If context is + * null, it is not considered; this should be used to build user + * interfaces, but not to authorise. + */ + static boolean implied(QName name, Subject subject, String context) { + Set roles = subject.getPrincipals(ImpliedByPrincipal.class); + for (ImpliedByPrincipal role : roles) { + if (role.isSystemRole()) { + if (role.getRoleName().equals(name)) { + // !! if context is not specified, it is considered irrelevant + if (context == null) + return true; + if (role.getContext().equalsIgnoreCase(context) + || role.getContext().equals(CmsConstants.NODE_BASEDN)) + return true; + } + } + } + return false; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java new file mode 100644 index 000000000..16f39609e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java @@ -0,0 +1,248 @@ +package org.argeo.cms.acr; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.util.LangUtils; + +/** Partial reference implementation of a {@link ProvidedContent}. */ +public abstract class AbstractContent extends AbstractMap implements ProvidedContent { + private final ProvidedSession session; + + // cache +// private String _path = null; + + public AbstractContent(ProvidedSession session) { + this.session = session; + } + + /* + * ATTRIBUTES OPERATIONS + */ +// protected abstract Iterable keys(); +// +// protected abstract void removeAttr(QName key); + + @Override + public Set> entrySet() { + Set> result = new AttrSet(); + return result; + } + + @Override + public Class getType(QName key) { + return String.class; + } + + @Override + public boolean isMultiple(QName key) { + return false; + } + + @SuppressWarnings("unchecked") + @Override + public List getMultiple(QName key, Class clss) { + Object value = get(key); + if (value == null) + return new ArrayList<>(); + if (value instanceof List) { + if (isDefaultAttrTypeRequested(clss)) + return (List) value; + List res = new ArrayList<>(); + List lst = (List) value; + for (Object o : lst) { + A item = clss.isAssignableFrom(String.class) ? (A) o.toString() : (A) o; + res.add(item); + } + return res; + } else {// singleton +// try { + A res = (A) value; + return Collections.singletonList(res); +// } catch (ClassCastException e) { +// return Optional.empty(); +// } + } + } + + /* + * CONTENT OPERATIONS + */ + + @Override + public String getPath() { +// if (_path != null) +// return _path; + List ancestors = new ArrayList<>(); + collectAncestors(ancestors, this); + StringBuilder path = new StringBuilder(); + ancestors: for (Content c : ancestors) { + QName name = c.getName(); + if (CrName.root.qName().equals(name)) + continue ancestors; + + path.append('/'); + path.append(NamespaceUtils.toPrefixedName(name)); + int siblingIndex = c.getSiblingIndex(); + if (siblingIndex != 1) + path.append('[').append(siblingIndex).append(']'); + } +// _path = path.toString(); +// return _path; + return path.toString(); + } + + private void collectAncestors(List ancestors, Content content) { + if (content == null) + return; + ancestors.add(0, content); + collectAncestors(ancestors, content.getParent()); + } + + @Override + public int getDepth() { + List ancestors = new ArrayList<>(); + collectAncestors(ancestors, this); + return ancestors.size(); + } + + @Override + public String getSessionLocalId() { + return getPath(); + } + + /* + * SESSION + */ + + @Override + public ProvidedSession getSession() { + return session; + } + + /* + * TYPING + */ + + @Override + public List getContentClasses() { + return new ArrayList<>(); + } + + /* + * UTILITIES + */ + protected boolean isDefaultAttrTypeRequested(Class clss) { + // check whether clss is Object.class + return clss.isAssignableFrom(Object.class); + } + +// @Override +// public String toString() { +// return "content " + getPath(); +// } + + /* + * DEFAULTS + */ + // - no children + // - no attributes + // - cannot be modified + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + @Override + public Content add(QName name, QName... classes) { + throw new UnsupportedOperationException("Content cannot be added."); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Content cannot be removed."); + } + + protected Iterable keys() { + return Collections.emptySet(); + } + + @Override + public Optional get(QName key, Class clss) { + return null; + } + + protected void removeAttr(QName key) { + throw new UnsupportedOperationException("Attributes cannot be removed."); + } + + /* + * SUB CLASSES + */ + + class AttrSet extends AbstractSet> { + + @Override + public Iterator> iterator() { + final Iterator keys = keys().iterator(); + Iterator> it = new Iterator>() { + + QName key = null; + + @Override + public boolean hasNext() { + return keys.hasNext(); + } + + @Override + public Entry next() { + key = keys.next(); + // TODO check type + Optional value = get(key, Object.class); + assert !value.isEmpty(); + AbstractMap.SimpleEntry entry = new SimpleEntry<>(key, value.get()); + return entry; + } + + @Override + public void remove() { + if (key != null) { + AbstractContent.this.removeAttr(key); + } else { + throw new IllegalStateException("Iteration has not started"); + } + } + + }; + return it; + } + + @Override + public int size() { + return LangUtils.size(keys()); + } + + } + + /* + * OBJECT METHODS + */ + @Override + public String toString() { + return "content " + getPath(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java new file mode 100644 index 000000000..c1f1ef5f3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java @@ -0,0 +1,228 @@ +package org.argeo.cms.acr; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.Content; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.RuntimeNamespaceContext; +import org.argeo.api.acr.spi.ContentNamespace; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.acr.xml.DomContentProvider; +import org.argeo.cms.acr.xml.DomUtils; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +/** + * Base implementation of a {@link ProvidedRepository}. + */ +public abstract class AbstractContentRepository implements ProvidedRepository { + private final static CmsLog log = CmsLog.getLog(AbstractContentRepository.class); + + private MountManager mountManager; + private TypesManager typesManager; + + private CmsContentSession systemSession; + + private Set providersToAdd = new HashSet<>(); + + // utilities + /** Should be used only to copy source and results. */ + private TransformerFactory identityTransformerFactory = TransformerFactory.newInstance(); + +// public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path"; + + public AbstractContentRepository() { + long begin = System.currentTimeMillis(); + // types + typesManager = new TypesManager(); + typesManager.init(); + Set types = typesManager.listTypes(); + if (log.isTraceEnabled()) + for (QName type : types) { + log.trace(type + " - " + typesManager.getAttributeTypes(type)); + } + long duration = System.currentTimeMillis() - begin; + log.debug(() -> "CMS content types available (initialisation took " + duration + " ms)"); + } + + protected abstract CmsContentSession newSystemSession(); + + public void start() { + systemSession = newSystemSession(); + // mounts + mountManager = new MountManager(systemSession); + } + + public void stop() { + systemSession.close(); + systemSession = null; + } + + /* + * REPOSITORY + */ + @Override + public void addProvider(ContentProvider provider) { + if (mountManager == null) { + providersToAdd.add(provider); + log.debug( + () -> "Will add provider " + provider.getMountPath() + " (" + provider.getClass().getName() + ")"); + } else { + mountManager.addStructuralContentProvider(provider); + log.debug(() -> "Added provider " + provider.getMountPath() + " (" + provider.getClass().getName() + ")"); + } + } + + @Override + public void registerTypes(ContentNamespace... namespaces) { + typesManager.registerTypes(namespaces); + } + + /* + * FACTORIES + */ + public void initRootContentProvider(Path path) { + try { +// DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); +// factory.setNamespaceAware(true); +// factory.setXIncludeAware(true); +// factory.setSchema(contentTypesManager.getSchema()); +// + DocumentBuilder dBuilder = typesManager.newDocumentBuilder(); + + Document document; +// if (path != null && Files.exists(path)) { +// InputSource inputSource = new InputSource(path.toAbsolutePath().toUri().toString()); +// inputSource.setEncoding(StandardCharsets.UTF_8.name()); +// // TODO public id as well? +// document = dBuilder.parse(inputSource); +// } else { + document = dBuilder.newDocument(); + Element root = document.createElementNS(ArgeoNamespace.CR_NAMESPACE_URI, + NamespaceUtils.toPrefixedName(CrName.root.qName())); + + for (String prefix : RuntimeNamespaceContext.getPrefixes().keySet()) { +// root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix, +// contentTypesManager.getPrefixes().get(prefix)); + DomUtils.addNamespace(root, prefix, + RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix)); + } + + document.appendChild(root); + + // write it + if (path != null) { + try (OutputStream out = Files.newOutputStream(path)) { + writeDom(document, out); + } + } +// } + + String mountPath = "/"; + DomContentProvider contentProvider = new DomContentProvider(mountPath, document); + addProvider(contentProvider); + } catch (DOMException | IOException e) { + throw new IllegalStateException("Cannot init ACR root " + path, e); + } + + // add content providers already notified + for (ContentProvider contentProvider : providersToAdd) + addProvider(contentProvider); + providersToAdd.clear(); + } + + public void writeDom(Document document, OutputStream out) throws IOException { + try { + Transformer transformer = identityTransformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + DOMSource source = new DOMSource(document); + typesManager.validate(source); + StreamResult result = new StreamResult(out); + transformer.transform(source, result); + } catch (TransformerException e) { + throw new IOException("Cannot write dom", e); + } + } + + /* + * MOUNT MANAGEMENT + */ + + @Override + public ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types) { + String mountPath = mountPoint.getPath(); + // TODO check consistency with types + + return mountManager.getOrAddMountedProvider(mountPath, (path) -> { + DocumentBuilder dBuilder = typesManager.newDocumentBuilder(); + Document document; + if (initialize) { + QName firstType = types[0]; + document = dBuilder.newDocument(); + String prefix = ((ProvidedContent) mountPoint).getSession().getPrefix(firstType.getNamespaceURI()); + Element root = document.createElementNS(firstType.getNamespaceURI(), + prefix + ":" + firstType.getLocalPart()); + DomUtils.addNamespace(root, prefix, firstType.getNamespaceURI()); + document.appendChild(root); + } else { + try (InputStream in = mountPoint.open(InputStream.class)) { + document = dBuilder.parse(in); + // TODO check consistency with types + } catch (IOException | SAXException e) { + throw new IllegalStateException("Cannot load mount from " + mountPoint, e); + } + } + DomContentProvider contentProvider = new DomContentProvider(path, document); + return contentProvider; + }); + } + + @Override + public boolean shouldMount(QName... types) { + if (types.length == 0) + return false; + QName firstType = types[0]; + Set registeredTypes = typesManager.listTypes(); + if (registeredTypes.contains(firstType)) + return true; + return false; + } + + MountManager getMountManager() { + return mountManager; + } + + TypesManager getTypesManager() { + return typesManager; + } + + CmsContentSession getSystemSession() { + return systemSession; + } + + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java new file mode 100644 index 000000000..429b759fc --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java @@ -0,0 +1,85 @@ +package org.argeo.cms.acr; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.spi.ContentNamespace; + +/** Content namespaces supported by CMS. */ +public enum CmsContentNamespace implements ContentNamespace { + // + // ARGEO + // + CR(ArgeoNamespace.CR_DEFAULT_PREFIX, ArgeoNamespace.CR_NAMESPACE_URI, "cr.xsd", null), + // + SLC("slc", "http://www.argeo.org/ns/slc", null, null), + // + ARGEO("argeo", "http://www.argeo.org/ns/argeo", null, null), + // + // EXTERNAL + // + XSD("xs", "http://www.w3.org/2001/XMLSchema", "XMLSchema.xsd", "http://www.w3.org/2001/XMLSchema.xsd"), + // + XML("xml", "http://www.w3.org/XML/1998/namespace", "xml.xsd", "http://www.w3.org/2001/xml.xsd"), + // + XLINK("xlink", "http://www.w3.org/1999/xlink", "xlink.xsd", "https://www.w3.org/1999/xlink.xsd"), + // + WEBDAV("D", "DAV:", null, "https://raw.githubusercontent.com/lookfirst/sardine/master/webdav.xsd"), + // + XSLT("xsl", "http://www.w3.org/1999/XSL/Transform", "schema-for-xslt20.xsd", + "https://www.w3.org/2007/schema-for-xslt20.xsd"), + // + SVG("svg", "http://www.w3.org/2000/svg", "SVG.xsd", + "https://raw.githubusercontent.com/oreillymedia/HTMLBook/master/schema/svg/SVG.xsd"), + // + DSML("dsml", "urn:oasis:names:tc:DSML:2:0:core", "DSMLv2.xsd", + "https://www.oasis-open.org/committees/dsml/docs/DSMLv2.xsd"), + // + ; + + private final static String RESOURCE_BASE = "/org/argeo/cms/acr/schemas/"; + + private String defaultPrefix; + private String namespace; + private URL resource; + private URL publicUrl; + + CmsContentNamespace(String defaultPrefix, String namespace, String resourceFileName, String publicUrl) { + Objects.requireNonNull(namespace); + this.defaultPrefix = defaultPrefix; + Objects.requireNonNull(namespace); + this.namespace = namespace; + if (resourceFileName != null) { + resource = getClass().getResource(RESOURCE_BASE + resourceFileName); + Objects.requireNonNull(resource); + } + if (publicUrl != null) + try { + this.publicUrl = new URL(publicUrl); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot interpret public URL", e); + } + } + + @Override + public String getDefaultPrefix() { + return defaultPrefix; + } + + @Override + public String getNamespaceURI() { + return namespace; + } + + @Override + public URL getSchemaResource() { + return resource; + } + + public URL getPublicUrl() { + return publicUrl; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java index 9cd8bd22d..3b47c1630 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -1,43 +1,35 @@ package org.argeo.cms.acr; -import java.security.AccessController; +import java.util.Collections; +import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.NavigableMap; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Collectors; -import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; -import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; -import org.argeo.api.acr.CrName; -import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedRepository; -import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.CmsState; +import org.argeo.api.cms.DataAdminPrincipal; +import org.argeo.api.uuid.UuidFactory; +import org.argeo.cms.CurrentUser; import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.util.CurrentSubject; -public class CmsContentRepository implements ProvidedRepository { - private NavigableMap partitions = new TreeMap<>(); +/** + * Multi-session {@link ProvidedRepository}, integrated with a CMS. + */ +public class CmsContentRepository extends AbstractContentRepository { + public final static String RUN_BASE = "/run"; + public final static String DIRECTORY_BASE = "/directory"; - // TODO synchronize ? - private NavigableMap prefixes = new TreeMap<>(); + private Map userSessions = Collections.synchronizedMap(new HashMap<>()); - public CmsContentRepository() { - prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI); - prefixes.put("basic", CrName.CR_NAMESPACE_URI); - prefixes.put("owner", CrName.CR_NAMESPACE_URI); - prefixes.put("posix", CrName.CR_NAMESPACE_URI); - } - - public void start() { - - } - - public void stop() { - - } + private CmsState cmsState; + private UuidFactory uuidFactory; /* * REPOSITORY @@ -50,90 +42,52 @@ public class CmsContentRepository implements ProvidedRepository { @Override public ContentSession get(Locale locale) { - Subject subject = Subject.getSubject(AccessController.getContext()); - return new CmsContentSession(subject, locale); - } - - public void addProvider(String base, ContentProvider provider) { - partitions.put(base, provider); - } - - public void registerPrefix(String prefix, String namespaceURI) { - String registeredUri = prefixes.get(prefix); - if (registeredUri == null) { - prefixes.put(prefix, namespaceURI); - return; - } - if (!registeredUri.equals(namespaceURI)) - throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri); - // do nothing if same namespace is already registered - } - - /* - * NAMESPACE CONTEXT - */ - - /* - * SESSION - */ - - class CmsContentSession implements ProvidedSession { - private Subject subject; - private Locale locale; - - public CmsContentSession(Subject subject, Locale locale) { - this.subject = subject; - this.locale = locale; - } - - @Override - public Content get(String path) { - Map.Entry entry = partitions.floorEntry(path); - String mountPath = entry.getKey(); - ContentProvider provider = entry.getValue(); - String relativePath = path.substring(mountPath.length()); - return provider.get(CmsContentSession.this, mountPath, relativePath); - } - - @Override - public Subject getSubject() { - return subject; - } - - @Override - public Locale getLocale() { - return locale; + if (!CmsSession.hasCmsSession(CurrentSubject.current())) { + if (DataAdminPrincipal.isDataAdmin(CurrentSubject.current())) { + // TODO open multiple data admin sessions? + return getSystemSession(); + } + throw new IllegalStateException("Caller must be authenticated"); } - @Override - public ProvidedRepository getRepository() { - return CmsContentRepository.this; + CmsSession cmsSession = CurrentUser.getCmsSession(); + CmsContentSession contentSession = userSessions.get(cmsSession); + if (contentSession == null) { + final CmsContentSession newContentSession = new CmsContentSession(this, cmsSession.getUuid(), + cmsSession.getSubject(), locale, uuidFactory); + cmsSession.addOnCloseCallback((c) -> { + newContentSession.close(); + userSessions.remove(cmsSession); + }); + contentSession = newContentSession; } + return contentSession; + } - /* - * NAMESPACE CONTEXT - */ - - @Override - public String findNamespace(String prefix) { - return prefixes.get(prefix); + @Override + protected CmsContentSession newSystemSession() { + LoginContext loginContext; + try { + loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName()); + loginContext.login(); + } catch (LoginException e1) { + throw new RuntimeException("Could not login as data admin", e1); + } finally { } + return new CmsContentSession(this, getCmsState().getUuid(), loginContext.getSubject(), Locale.getDefault(), + uuidFactory); + } - @Override - public Set findPrefixes(String namespaceURI) { - Set res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI)) - .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()); - - return res; - } + protected CmsState getCmsState() { + return cmsState; + } - @Override - public String findPrefix(String namespaceURI) { - if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX)) - return CrName.CR_DEFAULT_PREFIX; - return ProvidedSession.super.findPrefix(namespaceURI); - } + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + public void setUuidFactory(UuidFactory uuidFactory) { + this.uuidFactory = uuidFactory; } } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java new file mode 100644 index 000000000..17a03fdc5 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java @@ -0,0 +1,173 @@ +package org.argeo.cms.acr; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import javax.security.auth.Subject; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.DName; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.api.uuid.UuidFactory; +import org.argeo.cms.acr.xml.DomContentProvider; + +/** Implements {@link ProvidedSession}. */ +class CmsContentSession implements ProvidedSession { + final private AbstractContentRepository contentRepository; + + private final UUID uuid; + private Subject subject; + private Locale locale; + + private UuidFactory uuidFactory; + + private CompletableFuture closed = new CompletableFuture<>(); + + private CompletableFuture edition; + + private Set modifiedProviders = new HashSet<>(); + + private Content sessionRunDir; + + public CmsContentSession(AbstractContentRepository contentRepository, UUID uuid, Subject subject, Locale locale, + UuidFactory uuidFactory) { + this.contentRepository = contentRepository; + this.subject = subject; + this.locale = locale; + this.uuid = uuid; + this.uuidFactory = uuidFactory; + } + + public void close() { + closed.complete(this); + + if (sessionRunDir != null) + sessionRunDir.remove(); + } + + @Override + public CompletionStage onClose() { + return closed.minimalCompletionStage(); + } + + @Override + public Content get(String path) { + if (!path.startsWith(ContentUtils.ROOT_SLASH)) + throw new IllegalArgumentException(path + " is not an absolute path"); + ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path); + String mountPath = contentProvider.getMountPath(); + String relativePath = extractRelativePath(mountPath, path); + ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath); + return content; + } + + @Override + public boolean exists(String path) { + if (!path.startsWith(ContentUtils.ROOT_SLASH)) + throw new IllegalArgumentException(path + " is not an absolute path"); + ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path); + String mountPath = contentProvider.getMountPath(); + String relativePath = extractRelativePath(mountPath, path); + return contentProvider.exists(this, relativePath); + } + + private String extractRelativePath(String mountPath, String path) { + String relativePath = path.substring(mountPath.length()); + if (relativePath.length() > 0 && relativePath.charAt(0) == '/') + relativePath = relativePath.substring(1); + return relativePath; + } + + @Override + public Subject getSubject() { + return subject; + } + + @Override + public Locale getLocale() { + return locale; + } + + @Override + public ProvidedRepository getRepository() { + return contentRepository; + } + + public UuidFactory getUuidFactory() { + return uuidFactory; + } + + /* + * MOUNT MANAGEMENT + */ + @Override + public Content getMountPoint(String path) { + String[] parent = ContentUtils.getParentPath(path); + ProvidedContent mountParent = (ProvidedContent) get(parent[0]); +// Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path); + return mountParent.getMountPoint(parent[1]); + } + + /* + * EDITION + */ + @Override + public CompletionStage edit(Consumer work) { + edition = CompletableFuture.supplyAsync(() -> { + work.accept(this); + return this; + }).thenApply((s) -> { + synchronized (CmsContentSession.this) { + // TODO optimise + for (ContentProvider provider : modifiedProviders) { + if (provider instanceof DomContentProvider) { + ((DomContentProvider) provider).persist(s); + } + } + modifiedProviders.clear(); + return s; + } + }); + return edition.minimalCompletionStage(); + } + + @Override + public boolean isEditing() { + return edition != null && !edition.isDone(); + } + + @Override + public synchronized void notifyModification(ProvidedContent content) { + ContentProvider contentProvider = content.getProvider(); + modifiedProviders.add(contentProvider); + } + + @Override + public UUID getUuid() { + return uuid; + } + +// @Override + public Content getSessionRunDir() { + if (sessionRunDir == null) { + String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString(); + if (exists(runDirPath)) + sessionRunDir = get(runDirPath); + else { + Content runDir = get(CmsContentRepository.RUN_BASE); + // TODO deal with no run dir available? + sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName()); + } + } + return sessionRunDir; + } +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java new file mode 100644 index 000000000..d324ac475 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java @@ -0,0 +1,203 @@ +package org.argeo.cms.acr; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; +import java.util.function.BiConsumer; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentRepository; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.DName; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.directory.CmsDirectory; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.directory.UserDirectory; +import org.argeo.cms.util.CurrentSubject; +import org.osgi.service.useradmin.Role; + +/** Utilities and routines around {@link Content}. */ +public class ContentUtils { + public static void traverse(Content content, BiConsumer doIt) { + traverse(content, doIt, (Integer) null); + } + + public static void traverse(Content content, BiConsumer doIt, Integer maxDepth) { + doTraverse(content, doIt, 0, maxDepth); + } + + private static void doTraverse(Content content, BiConsumer doIt, int currentDepth, + Integer maxDepth) { + doIt.accept(content, currentDepth); + if (maxDepth != null && currentDepth == maxDepth) + return; + int nextDepth = currentDepth + 1; + for (Content child : content) { + doTraverse(child, doIt, nextDepth, maxDepth); + } + } + + public static void print(Content content, PrintStream out, int depth, boolean printText) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < depth; i++) { + sb.append(" "); + } + String prefix = sb.toString(); + out.println(prefix + content.getName()); + for (QName key : content.keySet()) { + out.println(prefix + " " + key + "=" + content.get(key)); + } + if (printText) { + if (content.hasText()) { + out.println(""); + } + } + } + +// public static boolean isString(T t) { +// return t instanceof String; +// } + + public static final char SLASH = '/'; + public static final String SLASH_STRING = Character.toString(SLASH); + public static final String ROOT_SLASH = "" + SLASH; + public static final String EMPTY = ""; + + /** + * Split a path (with '/' separator) in an array of length 2, the first part + * being the parent path (which could be either absolute or relative), the + * second one being the last segment, (guaranteed to be without a '/'). + */ + public static String[] getParentPath(String path) { + if (path == null) + throw new IllegalArgumentException("Path cannot be null"); + if (path.length() == 0) + throw new IllegalArgumentException("Path cannot be empty"); + checkDoubleSlash(path); + int parentIndex = path.lastIndexOf(SLASH); + if (parentIndex == path.length() - 1) {// trailing '/' + path = path.substring(0, path.length() - 1); + parentIndex = path.lastIndexOf(SLASH); + } + + if (parentIndex == -1) // no '/' + return new String[] { EMPTY, path }; + + return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH, + path.substring(parentIndex + 1) }; + } + + public static String toPath(List segments) { + // TODO checks + StringJoiner sj = new StringJoiner("/"); + segments.forEach((s) -> sj.add(s)); + return sj.toString(); + } + + public static List toPathSegments(String path) { + List res = new ArrayList<>(); + if (EMPTY.equals(path) || ROOT_SLASH.equals(path)) + return res; + collectPathSegments(path, res); + return res; + } + + private static void collectPathSegments(String path, List segments) { + String[] parent = getParentPath(path); + if (EMPTY.equals(parent[1])) // root + return; + segments.add(0, parent[1]); + if (EMPTY.equals(parent[0])) // end + return; + collectPathSegments(parent[0], segments); + } + + public static void checkDoubleSlash(String path) { + if (path.contains(SLASH + "" + SLASH)) + throw new IllegalArgumentException("Path " + path + " contains //"); + } + + /* + * DIRECTORY + */ + + public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) { + UserDirectory userDirectory = userManager.getDirectory(role); + String path = directoryPath(userDirectory) + userDirectory.getRolePath(role); + Content content = contentSession.get(path); + return content; + } + + public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) { + CmsDirectory directory = hierarchyUnit.getDirectory(); + StringJoiner relativePath = new StringJoiner(SLASH_STRING); + buildHierarchyUnitPath(hierarchyUnit, relativePath); + String path = directoryPath(directory) + relativePath.toString(); + Content content = contentSession.get(path); + return content; + } + + /** The path to this {@link CmsDirectory}. Ends with a /. */ + private static String directoryPath(CmsDirectory directory) { + return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH; + } + + /** Recursively build a relative path of a {@link HierarchyUnit}. */ + private static void buildHierarchyUnitPath(HierarchyUnit current, StringJoiner relativePath) { + if (current.getParent() == null) // directory + return; + buildHierarchyUnitPath(current.getParent(), relativePath); + relativePath.add(current.getHierarchyUnitName()); + } + + /* + * CONSUMER UTILS + */ + + public static Content createCollections(ContentSession session, String path) { + if (session.exists(path)) { + Content content = session.get(path); + if (!content.isContentClass(DName.collection.qName())) { + throw new IllegalStateException("Content " + path + " already exists, but is not a collection"); + } else { + return content; + } + } else { + String[] parentPath = getParentPath(path); + Content parent = createCollections(session, parentPath[0]); + Content content = parent.add(parentPath[1], DName.collection.qName()); + return content; + } + } + + public static ContentSession openDataAdminSession(ContentRepository repository) { + LoginContext loginContext; + try { + loginContext = CmsAuth.DATA_ADMIN.newLoginContext(); + loginContext.login(); + } catch (LoginException e1) { + throw new RuntimeException("Could not login as data admin", e1); + } finally { + } + + ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(ContentUtils.class.getClassLoader()); + return CurrentSubject.callAs(loginContext.getSubject(), () -> repository.get()); + } finally { + Thread.currentThread().setContextClassLoader(currentCl); + } + } + + /** Singleton. */ + private ContentUtils() { + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java new file mode 100644 index 000000000..36b0cfe5e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java @@ -0,0 +1,68 @@ +package org.argeo.cms.acr; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.Function; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.spi.ContentProvider; + +/** Manages the structural and dynamic mounts within the content repository. */ +class MountManager { + private final NavigableMap partitions = new TreeMap<>(); + + private final CmsContentSession systemSession; + + public MountManager(CmsContentSession systemSession) { + this.systemSession = systemSession; + } + + synchronized void addStructuralContentProvider(ContentProvider contentProvider) { + String mountPath = contentProvider.getMountPath(); + Objects.requireNonNull(mountPath); + if (partitions.containsKey(mountPath)) + throw new IllegalStateException("A provider is already registered for " + mountPath); + partitions.put(mountPath, contentProvider); + if ("/".equals(mountPath))// root + return; + String[] parentPath = ContentUtils.getParentPath(mountPath); + Content parent = systemSession.get(parentPath[0]); + Content mount = parent.add(parentPath[1]); + mount.put(CrName.mount.qName(), "true"); + + } + + synchronized ContentProvider getOrAddMountedProvider(String mountPath, Function factory) { + Objects.requireNonNull(factory); + if (!partitions.containsKey(mountPath)) { + ContentProvider contentProvider = factory.apply(mountPath); + if (!mountPath.equals(contentProvider.getMountPath())) + throw new IllegalArgumentException("Mount path " + mountPath + " is inconsistent with content provider " + + contentProvider.getMountPath()); + partitions.put(mountPath, contentProvider); + } + return partitions.get(mountPath); + } + + synchronized ContentProvider findContentProvider(String path) { +// if (ContentUtils.EMPTY.equals(path)) +// return partitions.firstEntry().getValue(); + Map.Entry entry = partitions.floorEntry(path); + if (entry == null) + throw new IllegalArgumentException("No entry provider found for path '" + path + "'"); + String mountPath = entry.getKey(); + if (!path.startsWith(mountPath)) { + // FIXME make it more robust and find when there is no content provider + String[] parent = ContentUtils.getParentPath(path); + return findContentProvider(parent[0]); + // throw new IllegalArgumentException("Path " + path + " doesn't have a content + // provider"); + } + ContentProvider contentProvider = entry.getValue(); + assert mountPath.equals(contentProvider.getMountPath()); + return contentProvider; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java new file mode 100644 index 000000000..b9b940f05 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java @@ -0,0 +1,104 @@ +package org.argeo.cms.acr; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; +import java.util.Objects; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; + +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.api.uuid.MacAddressUuidFactory; +import org.argeo.api.uuid.UuidFactory; +import org.argeo.cms.acr.fs.FsContentProvider; + +/** + * A standalone {@link ProvidedRepository} with a single {@link Subject} (which + * also provides the system session). + */ +public class SingleUserContentRepository extends AbstractContentRepository { + private final Subject subject; + private final Locale locale; + + private UUID uuid; + + private UuidFactory uuidFactory = new MacAddressUuidFactory(); + + // the single session + private CmsContentSession contentSession; + + public SingleUserContentRepository(Subject subject) { + this(subject, Locale.getDefault()); + } + + public SingleUserContentRepository(Subject subject, Locale locale) { + Objects.requireNonNull(subject); + Objects.requireNonNull(locale); + + this.subject = subject; + this.locale = locale; + + // TODO use an UUID factory + this.uuid = UUID.randomUUID(); + } + + @Override + public void start() { + Objects.requireNonNull(subject); + Objects.requireNonNull(locale); + + super.start(); + initRootContentProvider(null); + if (contentSession != null) + throw new IllegalStateException("Repository is already started, stop it first."); + contentSession = new CmsContentSession(this, uuid, subject, locale, uuidFactory); + } + + @Override + public void stop() { + if (contentSession != null) + contentSession.close(); + contentSession = null; + super.stop(); + } + + @Override + public ContentSession get(Locale locale) { + if (!this.locale.equals(locale)) + throw new UnsupportedOperationException("This repository does not support multi-locale sessions"); + return contentSession; + } + + @Override + public ContentSession get() { + return contentSession; + } + + @Override + protected CmsContentSession newSystemSession() { + return new CmsContentSession(this, uuid, subject, Locale.getDefault(), uuidFactory); + } + + public static void main(String... args) { + Path homePath = Paths.get(System.getProperty("user.home")); + String username = System.getProperty("user.name"); + X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + ",dc=localhost"); + Subject subject = new Subject(); + subject.getPrincipals().add(principal); + + SingleUserContentRepository contentRepository = new SingleUserContentRepository(subject); + contentRepository.start(); + FsContentProvider homeContentProvider = new FsContentProvider("/home", homePath); + contentRepository.addProvider(homeContentProvider); + Runtime.getRuntime().addShutdownHook(new Thread(() -> contentRepository.stop(), "Shutdown content repository")); + + ContentSession contentSession = contentRepository.get(); + ContentUtils.traverse(contentSession.get("/"), (c, depth) -> ContentUtils.print(c, System.out, depth, false), + 2); + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java new file mode 100644 index 000000000..61d8e0429 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java @@ -0,0 +1,31 @@ +package org.argeo.cms.acr; + +import org.argeo.api.acr.QNamed; + +public enum SvgAttrs implements QNamed { + /** */ + width, + /** */ + height, + /** */ + length, + /** */ + unit, + /** */ + dur, + /** */ + direction, + // + ; + + @Override + public String getNamespace() { + return CmsContentNamespace.SVG.getNamespaceURI(); + } + + @Override + public String getDefaultPrefix() { + return CmsContentNamespace.SVG.getDefaultPrefix(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java new file mode 100644 index 000000000..c3bea5b60 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java @@ -0,0 +1,492 @@ +package org.argeo.cms.acr; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; + +import org.apache.xerces.impl.xs.XSImplementationImpl; +import org.apache.xerces.impl.xs.util.StringListImpl; +import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl; +import org.apache.xerces.xs.StringList; +import org.apache.xerces.xs.XSAttributeDeclaration; +import org.apache.xerces.xs.XSAttributeUse; +import org.apache.xerces.xs.XSComplexTypeDefinition; +import org.apache.xerces.xs.XSConstants; +import org.apache.xerces.xs.XSElementDeclaration; +import org.apache.xerces.xs.XSException; +import org.apache.xerces.xs.XSImplementation; +import org.apache.xerces.xs.XSLoader; +import org.apache.xerces.xs.XSModel; +import org.apache.xerces.xs.XSModelGroup; +import org.apache.xerces.xs.XSNamedMap; +import org.apache.xerces.xs.XSObjectList; +import org.apache.xerces.xs.XSParticle; +import org.apache.xerces.xs.XSSimpleTypeDefinition; +import org.apache.xerces.xs.XSTerm; +import org.apache.xerces.xs.XSTypeDefinition; +import org.argeo.api.acr.CrAttributeType; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.RuntimeNamespaceContext; +import org.argeo.api.acr.spi.ContentNamespace; +import org.argeo.api.cms.CmsLog; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** Register content types. */ +class TypesManager { + private final static CmsLog log = CmsLog.getLog(TypesManager.class); +// private Map prefixes = new TreeMap<>(); + + // immutable factories + private SchemaFactory schemaFactory; + + /** Schema sources. */ + private List sources = new ArrayList<>(); + + // cached + private Schema schema; + private DocumentBuilderFactory documentBuilderFactory; + private XSModel xsModel; + private SortedMap> types; + + private boolean validating = false; + private boolean creatingXsModel = false; + + private final static boolean limited = false; + + public TypesManager() { + schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + + // types + types = new TreeMap<>(NamespaceUtils.QNAME_COMPARATOR); + + } + + public void init() { + registerTypes(CmsContentNamespace.values()); + } + + public void registerTypes(ContentNamespace... namespaces) { +// if (prefixes.containsKey(defaultPrefix)) +// throw new IllegalStateException( +// "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix)); +// prefixes.put(defaultPrefix, namespace); + for (ContentNamespace contentNamespace : namespaces) { + RuntimeNamespaceContext.register(contentNamespace.getNamespaceURI(), contentNamespace.getDefaultPrefix()); + + if (contentNamespace.getSchemaResource() != null) { + sources.add(contentNamespace.getSchemaResource()); + log.debug(() -> "Registered types " + contentNamespace.getNamespaceURI() + " from " + + contentNamespace.getSchemaResource().toExternalForm()); + } + } + reload(); + } + + public Set listTypes() { + return types.keySet(); + } + + public Map getAttributeTypes(QName type) { + if (!types.containsKey(type)) + throw new IllegalArgumentException("Unkown type"); + return types.get(type); + } + + private synchronized void reload() { + try { + // schema + if (validating) { + List sourcesToUse = new ArrayList<>(); + for (URL sourceUrl : sources) { + sourcesToUse.add(new StreamSource(sourceUrl.toExternalForm())); + } + schema = schemaFactory.newSchema(sourcesToUse.toArray(new Source[sourcesToUse.size()])); +// for (StreamSource source : sourcesToUse) { +// try { +// source.getInputStream().close(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } + } + + // document builder factory + // we force usage of Xerces for predictability + documentBuilderFactory = limited ? DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl(); + documentBuilderFactory.setNamespaceAware(true); + if (!limited) { + documentBuilderFactory.setXIncludeAware(true); + if (validating) { + documentBuilderFactory.setSchema(getSchema()); + documentBuilderFactory.setValidating(validating); + } + } + + if (creatingXsModel) { + // XS model + // TODO use JVM implementation? +// DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); +// XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader"); + XSImplementation xsImplementation = new XSImplementationImpl(); + XSLoader xsLoader = xsImplementation.createXSLoader(null); + List systemIds = new ArrayList<>(); + for (URL sourceUrl : sources) { + systemIds.add(sourceUrl.toExternalForm()); + } + StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size()); + xsModel = xsLoader.loadURIList(sl); + + // types +// XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION); +// for (int i = 0; i < map.getLength(); i++) { +// XSElementDeclaration eDec = (XSElementDeclaration) map.item(i); +// QName type = new QName(eDec.getNamespace(), eDec.getName()); +// types.add(type); +// } + collectTypes(); + + log.debug("Created XS model"); + } + } catch (XSException | SAXException e) { + throw new IllegalStateException("Cannot reload types", e); + } + } + + private void collectTypes() { + types.clear(); + // elements + XSNamedMap topLevelElements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION); + for (int i = 0; i < topLevelElements.getLength(); i++) { + XSElementDeclaration eDec = (XSElementDeclaration) topLevelElements.item(i); + collectElementDeclaration("", eDec); + } + + // types + XSNamedMap topLevelTypes = xsModel.getComponents(XSConstants.TYPE_DEFINITION); + for (int i = 0; i < topLevelTypes.getLength(); i++) { + XSTypeDefinition tDef = (XSTypeDefinition) topLevelTypes.item(i); + collectType(tDef, null, null); + } + + } + + private void collectType(XSTypeDefinition tDef, String namespace, String nameHint) { + if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) { + XSComplexTypeDefinition ctDef = (XSComplexTypeDefinition) tDef; + if (ctDef.getContentType() != XSComplexTypeDefinition.CONTENTTYPE_SIMPLE + || ctDef.getAttributeUses().getLength() > 0 || ctDef.getAttributeWildcard() != null) { + collectComplexType("", null, ctDef); + } else { + throw new IllegalArgumentException("Unsupported type " + tDef.getTypeCategory()); + } + } + } + + private void collectComplexType(String prefix, QName parent, XSComplexTypeDefinition ctDef) { + if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE) { + + // content with attributes and a string value + + XSSimpleTypeDefinition stDef = ctDef.getSimpleType(); + // QName name = new QName(stDef.getNamespace(), stDef.getName()); + // log.warn(prefix + "Simple " + ctDef + " - " + attributes); +// System.err.println(prefix + "Simple from " + parent + " - " + attributes); +// +// if (parentAttributes != null) { +// for (QName attr : attributes.keySet()) { +// if (!parentAttributes.containsKey(attr)) +// System.err.println(prefix + " - " + attr + " not available in parent"); +// +// } +// } + + } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_ELEMENT + || ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED) { + XSParticle p = ctDef.getParticle(); + + collectParticle(prefix, p, false); + } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_EMPTY) { + // Parent only contains attributes +// if (parent != null) +// System.err.println(prefix + "Empty from " + parent + " - " + attributes); +// if (parentAttributes != null) { +// for (QName attr : attributes.keySet()) { +// if (!parentAttributes.containsKey(attr)) +// System.err.println(prefix + " - " + attr + " not available in parent"); +// +// } +// } +// log.debug(prefix + "Empty " + ctDef.getNamespace() + ":" + ctDef.getName() + " - " + attributes); + } else { + throw new IllegalArgumentException("Unsupported type " + ctDef.getTypeCategory()); + } + } + + private void collectParticle(String prefix, XSParticle particle, boolean multipleFromAbove) { + boolean orderable = false; + + XSTerm term = particle.getTerm(); + + if (particle.getMaxOccurs() == 0) { + return; + } + + boolean mandatory = false; + if (particle.getMinOccurs() > 0) { + mandatory = true; + } + + boolean multiple = false; + if (particle.getMaxOccurs() > 1 || particle.getMaxOccursUnbounded()) { + multiple = true; + } + if (!multiple && multipleFromAbove) + multiple = true; + + if (term.getType() == XSConstants.ELEMENT_DECLARATION) { + XSElementDeclaration eDec = (XSElementDeclaration) term; + + collectElementDeclaration(prefix, eDec); + // If this particle is a wildcard (an )then it + // is converted into a node def. + } else if (term.getType() == XSConstants.WILDCARD) { + // TODO can be anything + + // If this particle is a model group (one of + // , or ) then + // it subparticles must be processed. + } else if (term.getType() == XSConstants.MODEL_GROUP) { + XSModelGroup mg = (XSModelGroup) term; + + if (mg.getCompositor() == XSModelGroup.COMPOSITOR_SEQUENCE) { + orderable = true; + } + XSObjectList list = mg.getParticles(); + for (int i = 0; i < list.getLength(); i++) { + XSParticle pp = (XSParticle) list.item(i); + collectParticle(prefix + " ", pp, multiple); + } + } + } + + private void collectElementDeclaration(String prefix, XSElementDeclaration eDec) { + QName name = new QName(eDec.getNamespace(), eDec.getName()); + XSTypeDefinition tDef = eDec.getTypeDefinition(); + + XSComplexTypeDefinition ctDef = null; + Map attributes = new HashMap<>(); + if (tDef.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) { + XSSimpleTypeDefinition stDef = (XSSimpleTypeDefinition) tDef; +// System.err.println(prefix + "Simple element " + name); + } else if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) { + ctDef = (XSComplexTypeDefinition) tDef; + if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE + && ctDef.getAttributeUses().getLength() == 0 && ctDef.getAttributeWildcard() == null) { + XSSimpleTypeDefinition stDef = ctDef.getSimpleType(); +// System.err.println(prefix + "Simplified element " + name); + } else { + if (!types.containsKey(name)) { +// System.out.println(prefix + "Element " + name); + + XSObjectList list = ctDef.getAttributeUses(); + for (int i = 0; i < list.getLength(); i++) { + XSAttributeUse au = (XSAttributeUse) list.item(i); + XSAttributeDeclaration ad = au.getAttrDeclaration(); + QName attrName = new QName(ad.getNamespace(), ad.getName()); + // Get the simple type def for this attribute + XSSimpleTypeDefinition std = ad.getTypeDefinition(); + attributes.put(attrName, xsToCrType(std.getBuiltInKind())); +// System.out.println(prefix + " - " + attrName + " = " + attributes.get(attrName)); + } + // REGISTER + types.put(name, attributes); + if (ctDef != null) + collectComplexType(prefix + " ", name, ctDef); + } + } + } + + } + + private CrAttributeType xsToCrType(short kind) { + CrAttributeType propertyType; + switch (kind) { + case XSConstants.ANYSIMPLETYPE_DT: + case XSConstants.STRING_DT: + case XSConstants.ID_DT: + case XSConstants.ENTITY_DT: + case XSConstants.NOTATION_DT: + case XSConstants.NORMALIZEDSTRING_DT: + case XSConstants.TOKEN_DT: + case XSConstants.LANGUAGE_DT: + case XSConstants.NMTOKEN_DT: + propertyType = CrAttributeType.STRING; + break; + case XSConstants.BOOLEAN_DT: + propertyType = CrAttributeType.BOOLEAN; + break; + case XSConstants.DECIMAL_DT: + case XSConstants.FLOAT_DT: + case XSConstants.DOUBLE_DT: + propertyType = CrAttributeType.DOUBLE; + break; + case XSConstants.DURATION_DT: + case XSConstants.DATETIME_DT: + case XSConstants.TIME_DT: + case XSConstants.DATE_DT: + case XSConstants.GYEARMONTH_DT: + case XSConstants.GYEAR_DT: + case XSConstants.GMONTHDAY_DT: + case XSConstants.GDAY_DT: + case XSConstants.GMONTH_DT: + propertyType = CrAttributeType.DATE_TIME; + break; + case XSConstants.HEXBINARY_DT: + case XSConstants.BASE64BINARY_DT: + case XSConstants.ANYURI_DT: + propertyType = CrAttributeType.ANY_URI; + break; + case XSConstants.QNAME_DT: + case XSConstants.NAME_DT: + case XSConstants.NCNAME_DT: + // TODO support QName? + propertyType = CrAttributeType.STRING; + break; + case XSConstants.IDREF_DT: + // TODO support references? + propertyType = CrAttributeType.STRING; + break; + case XSConstants.INTEGER_DT: + case XSConstants.NONPOSITIVEINTEGER_DT: + case XSConstants.NEGATIVEINTEGER_DT: + case XSConstants.LONG_DT: + case XSConstants.INT_DT: + case XSConstants.SHORT_DT: + case XSConstants.BYTE_DT: + case XSConstants.NONNEGATIVEINTEGER_DT: + case XSConstants.UNSIGNEDLONG_DT: + case XSConstants.UNSIGNEDINT_DT: + case XSConstants.UNSIGNEDSHORT_DT: + case XSConstants.UNSIGNEDBYTE_DT: + case XSConstants.POSITIVEINTEGER_DT: + propertyType = CrAttributeType.LONG; + break; + case XSConstants.LISTOFUNION_DT: + case XSConstants.LIST_DT: + case XSConstants.UNAVAILABLE_DT: + propertyType = CrAttributeType.STRING; + break; + default: + propertyType = CrAttributeType.STRING; + break; + } + return propertyType; + } + + public DocumentBuilder newDocumentBuilder() { + try { + DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder(); + dBuilder.setErrorHandler(new ErrorHandler() { + + @Override + public void warning(SAXParseException exception) throws SAXException { + log.warn(exception); + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + log.error(exception); + } + + @Override + public void error(SAXParseException exception) throws SAXException { + log.error(exception); + } + }); + return dBuilder; + } catch (ParserConfigurationException e) { + throw new IllegalStateException("Cannot create document builder", e); + } + } + + public void printTypes() { + if (xsModel != null) + try { + + // Convert top level complex type definitions to node types + log.debug("\n## TYPES"); + XSNamedMap map = xsModel.getComponents(XSConstants.TYPE_DEFINITION); + for (int i = 0; i < map.getLength(); i++) { + XSTypeDefinition tDef = (XSTypeDefinition) map.item(i); + log.debug(tDef); + } + // Convert local (anonymous) complex type defs found in top level + // element declarations + log.debug("\n## ELEMENTS"); + map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION); + for (int i = 0; i < map.getLength(); i++) { + XSElementDeclaration eDec = (XSElementDeclaration) map.item(i); + XSTypeDefinition tDef = eDec.getTypeDefinition(); + log.debug(eDec + ", " + tDef); + } + log.debug("\n## ATTRIBUTES"); + map = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION); + for (int i = 0; i < map.getLength(); i++) { + XSAttributeDeclaration eDec = (XSAttributeDeclaration) map.item(i); + XSTypeDefinition tDef = eDec.getTypeDefinition(); + log.debug(eDec.getNamespace() + ":" + eDec.getName() + ", " + tDef); + } + } catch (ClassCastException | XSException e) { + throw new RuntimeException(e); + } + + } + + public void validate(Source source) throws IOException { + if (!validating) + return; + Validator validator; + synchronized (this) { + validator = schema.newValidator(); + } + try { + validator.validate(source); + } catch (SAXException e) { + log.error(source + " is not valid " + e); + // throw new IllegalArgumentException("Provided source is not valid", e); + } + } + +// public Map getPrefixes() { +// return prefixes; +// } + +// public List getSources() { +// return sources; +// } + + public Schema getSchema() { + return schema; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java new file mode 100644 index 000000000..96e6eeaf3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java @@ -0,0 +1,111 @@ +package org.argeo.cms.acr.dav; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.AbstractContent; +import org.argeo.cms.acr.ContentUtils; +import org.argeo.cms.dav.DavResponse; +import org.argeo.cms.http.HttpStatus; + +public class DavContent extends AbstractContent { + private final DavContentProvider provider; + private final URI uri; + + private Set keyNames; + private Optional> values; + + public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set keyNames) { + this(session, provider, uri, keyNames, Optional.empty()); + } + + public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set keyNames, + Optional> values) { + super(session); + this.provider = provider; + this.uri = uri; + this.keyNames = keyNames; + this.values = values; + } + + @Override + public QName getName() { + String fileName = ContentUtils.getParentPath(uri.getPath())[1]; + ContentName name = NamespaceUtils.parsePrefixedName(provider, fileName); + return name; + } + + @Override + public Content getParent() { + try { + String parentPath = ContentUtils.getParentPath(uri.getPath())[0]; + URI parentUri = new URI(uri.getScheme(), uri.getHost(), parentPath, null); + return provider.getDavContent(getSession(), parentUri); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot create parent", e); + } + } + + @Override + public Iterator iterator() { + Iterator responses = provider.getDavClient().listChildren(uri); + return new DavResponseIterator(responses); + } + + @Override + protected Iterable keys() { + return keyNames; + } + + @SuppressWarnings("unchecked") + @Override + public Optional get(QName key, Class clss) { + if (values.isEmpty()) { + DavResponse response = provider.getDavClient().get(uri); + values = Optional.of(response.getProperties()); + } + String valueStr = values.get().get(key); + if (valueStr == null) + return Optional.empty(); + // TODO convert + return Optional.of((A) valueStr); + } + + @Override + public ContentProvider getProvider() { + return provider; + } + + class DavResponseIterator implements Iterator { + private final Iterator responses; + + public DavResponseIterator(Iterator responses) { + this.responses = responses; + } + + @Override + public boolean hasNext() { + return responses.hasNext(); + } + + @Override + public Content next() { + DavResponse response = responses.next(); + String relativePath = response.getHref(); + URI contentUri = provider.relativePathToUri(relativePath); + return new DavContent(getSession(), provider, contentUri, response.getPropertyNames(HttpStatus.OK)); + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java new file mode 100644 index 000000000..374fcebbc --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java @@ -0,0 +1,77 @@ +package org.argeo.cms.acr.dav; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; + +import org.argeo.api.acr.RuntimeNamespaceContext; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.dav.DavClient; +import org.argeo.cms.dav.DavResponse; +import org.argeo.cms.http.HttpStatus; + +public class DavContentProvider implements ContentProvider { + private String mountPath; + private URI baseUri; + + private DavClient davClient; + + public DavContentProvider(String mountPath, URI baseUri) { + this.mountPath = mountPath; + this.baseUri = baseUri; + if (!baseUri.getPath().endsWith("/")) + throw new IllegalArgumentException("Base URI " + baseUri + " path does not end with /"); + this.davClient = new DavClient(); + } + + @Override + public String getNamespaceURI(String prefix) { + // FIXME retrieve mappings from WebDav + return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // FIXME retrieve mappings from WebDav + return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI); + } + + @Override + public ProvidedContent get(ProvidedSession session, String relativePath) { + URI contentUri = relativePathToUri(relativePath); + return getDavContent(session, contentUri); + } + + DavContent getDavContent(ProvidedSession session, URI uri) { + DavResponse response = davClient.get(uri); + return new DavContent(session, this, uri, response.getPropertyNames(HttpStatus.OK)); + } + + @Override + public boolean exists(ProvidedSession session, String relativePath) { + URI contentUri = relativePathToUri(relativePath); + return davClient.exists(contentUri); + } + + @Override + public String getMountPath() { + return mountPath; + } + + DavClient getDavClient() { + return davClient; + } + + URI relativePathToUri(String relativePath) { + try { + // TODO check last slash + String path = relativePath.startsWith("/") ? relativePath : baseUri.getPath() + relativePath; + URI uri = new URI(baseUri.getScheme(), baseUri.getHost(), path, baseUri.getFragment()); + return uri; + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot build URI for " + relativePath + " relatively to " + baseUri, e); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java new file mode 100644 index 000000000..b737b50a1 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java @@ -0,0 +1,98 @@ +package org.argeo.cms.acr.directory; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.CrAttributeType; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.ldap.LdapObj; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.AbstractContent; + +abstract class AbstractDirectoryContent extends AbstractContent { + protected final DirectoryContentProvider provider; + + public AbstractDirectoryContent(ProvidedSession session, DirectoryContentProvider provider) { + super(session); + this.provider = provider; + } + + abstract Dictionary doGetProperties(); + + @SuppressWarnings("unchecked") + @Override + public Optional get(QName key, Class clss) { + String attrName = key.getLocalPart(); + Object value = doGetProperties().get(attrName); + if (value == null) + return Optional.empty(); + Optional res = CrAttributeType.cast(clss, value); + if (res.isEmpty()) + return Optional.of((A) value); + else + return res; + } + + @Override + protected Iterable keys() { + Dictionary properties = doGetProperties(); + Set keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR); + keys: for (Enumeration it = properties.keys(); it.hasMoreElements();) { + String key = it.nextElement(); + if (key.equalsIgnoreCase(LdapAttr.objectClass.name())) + continue keys; + if (key.equalsIgnoreCase(LdapAttr.objectClasses.name())) + continue keys; + ContentName name = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, key, provider); + keys.add(name); + } + return keys; + } + + @Override + public List getContentClasses() { + Dictionary properties = doGetProperties(); + List contentClasses = new ArrayList<>(); + String objectClass = properties.get(LdapAttr.objectClass.name()).toString(); + contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, objectClass, provider)); + + String[] objectClasses = properties.get(LdapAttr.objectClasses.name()).toString().split("\\n"); + objectClasses: for (String oc : objectClasses) { + if (LdapObj.top.name().equalsIgnoreCase(oc)) + continue objectClasses; + if (objectClass.equalsIgnoreCase(oc)) + continue objectClasses; + contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, oc, provider)); + } + return contentClasses; + } + + @Override + public Object put(QName key, Object value) { + Object previous = get(key); + provider.getUserManager().edit(() -> doGetProperties().put(key.getLocalPart(), value)); + return previous; + } + + @Override + protected void removeAttr(QName key) { + doGetProperties().remove(key.getLocalPart()); + } + + @Override + public ContentProvider getProvider() { + return provider; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java new file mode 100644 index 000000000..6e39280ce --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java @@ -0,0 +1,58 @@ +package org.argeo.cms.acr.directory; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Iterator; +import java.util.List; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.api.cms.directory.CmsDirectory; +import org.argeo.api.cms.directory.HierarchyUnit; + +class DirectoryContent extends AbstractDirectoryContent { + private CmsDirectory directory; + + public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, CmsDirectory directory) { + super(session, provider); + this.directory = directory; + } + + @Override + Dictionary doGetProperties() { + return directory.getProperties(); + } + + @Override + public Iterator iterator() { + List res = new ArrayList<>(); + for (Iterator it = directory.getDirectHierarchyUnits(false).iterator(); it.hasNext();) { + res.add(new HierarchyUnitContent(getSession(), provider, it.next())); + } + return res.iterator(); + } + + @Override + public QName getName() { + return new ContentName(directory.getName()); + } + + @Override + public Content getParent() { + return provider.getRootContent(getSession()); + } + + @SuppressWarnings("unchecked") + @Override + public A adapt(Class clss) { + if (clss.equals(HierarchyUnit.class)) + return (A) directory; + if (clss.equals(CmsDirectory.class)) + return (A) directory; + return super.adapt(clss); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java new file mode 100644 index 000000000..8b6eb6bbd --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java @@ -0,0 +1,155 @@ +package org.argeo.cms.acr.directory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.ContentNotFoundException; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.directory.UserDirectory; +import org.argeo.cms.acr.AbstractContent; +import org.argeo.cms.acr.ContentUtils; +import org.osgi.service.useradmin.User; + +public class DirectoryContentProvider implements ContentProvider { + private String mountPath; + private String mountName; + + private CmsUserManager userManager; + + public DirectoryContentProvider(String mountPath, CmsUserManager userManager) { + this.mountPath = mountPath; + List mountSegments = ContentUtils.toPathSegments(mountPath); + this.mountName = mountSegments.get(mountSegments.size() - 1); + this.userManager = userManager; + } + + @Override + public ProvidedContent get(ProvidedSession session, String relativePath) { + List segments = ContentUtils.toPathSegments(relativePath); + if (segments.size() == 0) + return new UserManagerContent(session); + String userDirectoryName = segments.get(0); + UserDirectory userDirectory = null; + userDirectories: for (UserDirectory ud : userManager.getUserDirectories()) { + if (userDirectoryName.equals(ud.getName())) { + userDirectory = ud; + break userDirectories; + } + } + if (userDirectory == null) + throw new ContentNotFoundException(session, mountPath + "/" + relativePath, + "Cannot find user directory " + userDirectoryName); + if (segments.size() == 1) { + return new DirectoryContent(session, this, userDirectory); + } else { + List relSegments = new ArrayList<>(segments); + relSegments.remove(0); + String pathWithinUserDirectory = ContentUtils.toPath(relSegments); +// LdapName dn; +// try { +// dn = LdapNameUtils.toLdapName(userDirectoryDn); +// for (int i = 1; i < segments.size(); i++) { +// dn.add(segments.get(i)); +// } +// } catch (InvalidNameException e) { +// throw new IllegalStateException("Cannot interpret " + segments + " as DN", e); +// } + User user = (User) userDirectory.getRoleByPath(pathWithinUserDirectory); + if (user != null) { + HierarchyUnit parent = userDirectory.getHierarchyUnit(user); + return new RoleContent(session, this, new HierarchyUnitContent(session, this, parent), user); + } + HierarchyUnit hierarchyUnit = userDirectory.getHierarchyUnit(pathWithinUserDirectory); + if (hierarchyUnit == null) + throw new ContentNotFoundException(session, + mountPath + "/" + relativePath + "/" + pathWithinUserDirectory, + "Cannot find " + pathWithinUserDirectory + " within " + userDirectoryName); + return new HierarchyUnitContent(session, this, hierarchyUnit); + } + } + + @Override + public boolean exists(ProvidedSession session, String relativePath) { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getMountPath() { + return mountPath; + } + + @Override + public String getNamespaceURI(String prefix) { + if (ArgeoNamespace.LDAP_DEFAULT_PREFIX.equals(prefix)) + return ArgeoNamespace.LDAP_NAMESPACE_URI; + throw new IllegalArgumentException("Only prefix " + ArgeoNamespace.LDAP_DEFAULT_PREFIX + " is supported"); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + if (ArgeoNamespace.LDAP_NAMESPACE_URI.equals(namespaceURI)) + return Collections.singletonList(ArgeoNamespace.LDAP_DEFAULT_PREFIX).iterator(); + throw new IllegalArgumentException("Only namespace URI " + ArgeoNamespace.LDAP_NAMESPACE_URI + " is supported"); + } + + public void setUserManager(CmsUserManager userManager) { + this.userManager = userManager; + } + + public CmsUserManager getUserManager() { + return userManager; + } + + UserManagerContent getRootContent(ProvidedSession session) { + return new UserManagerContent(session); + } + + /* + * COMMON UTILITIES + */ + class UserManagerContent extends AbstractContent { + + public UserManagerContent(ProvidedSession session) { + super(session); + } + + @Override + public ContentProvider getProvider() { + return DirectoryContentProvider.this; + } + + @Override + public QName getName() { + return new ContentName(mountName); + } + + @Override + public Content getParent() { + return null; + } + + @Override + public Iterator iterator() { + List res = new ArrayList<>(); + for (UserDirectory userDirectory : userManager.getUserDirectories()) { + DirectoryContent content = new DirectoryContent(getSession(), DirectoryContentProvider.this, + userDirectory); + res.add(content); + } + return res.iterator(); + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java new file mode 100644 index 000000000..5acf8ab63 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java @@ -0,0 +1,92 @@ +package org.argeo.cms.acr.directory; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.DName; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.api.cms.directory.CmsDirectory; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.directory.UserDirectory; +import org.osgi.service.useradmin.Role; + +class HierarchyUnitContent extends AbstractDirectoryContent { + private HierarchyUnit hierarchyUnit; + + public HierarchyUnitContent(ProvidedSession session, DirectoryContentProvider provider, + HierarchyUnit hierarchyUnit) { + super(session, provider); + Objects.requireNonNull(hierarchyUnit); + this.hierarchyUnit = hierarchyUnit; + } + + @Override + Dictionary doGetProperties() { + return hierarchyUnit.getProperties(); + } + + @Override + public QName getName() { +// if (hierarchyUnit.getParent() == null) {// base DN +// String baseDn = hierarchyUnit.getBasePath(); +// return new ContentName(baseDn); +// } + String name = hierarchyUnit.getHierarchyUnitName(); + return new ContentName(name); + } + + @Override + public Content getParent() { + HierarchyUnit parentHu = hierarchyUnit.getParent(); + if (parentHu instanceof CmsDirectory) { + return new DirectoryContent(getSession(), provider, hierarchyUnit.getDirectory()); + } + return new HierarchyUnitContent(getSession(), provider, parentHu); + } + + @Override + public Iterator iterator() { + List lst = new ArrayList<>(); + for (HierarchyUnit hu : hierarchyUnit.getDirectHierarchyUnits(false)) + lst.add(new HierarchyUnitContent(getSession(), provider, hu)); + + for (Role role : ((UserDirectory) hierarchyUnit.getDirectory()).getHierarchyUnitRoles(hierarchyUnit, null, + false)) + lst.add(new RoleContent(getSession(), provider, this, role)); + return lst.iterator(); + } + + /* + * TYPING + */ + @Override + public List getContentClasses() { + List contentClasses = super.getContentClasses(); + contentClasses.add(DName.collection.qName()); + return contentClasses; + } + + @SuppressWarnings("unchecked") + @Override + public A adapt(Class clss) { + if (clss.equals(HierarchyUnit.class)) + return (A) hierarchyUnit; + return super.adapt(clss); + } + + /* + * ACCESSOR + */ + HierarchyUnit getHierarchyUnit() { + return hierarchyUnit; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java new file mode 100644 index 000000000..64feb1d67 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java @@ -0,0 +1,49 @@ +package org.argeo.cms.acr.directory; + +import java.util.Dictionary; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.api.cms.directory.UserDirectory; +import org.osgi.service.useradmin.Role; + +class RoleContent extends AbstractDirectoryContent { + + private HierarchyUnitContent parent; + private Role role; + + public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent, + Role role) { + super(session, provider); + this.parent = parent; + this.role = role; + } + + @Override + Dictionary doGetProperties() { + return role.getProperties(); + } + + @Override + public QName getName() { + String name = ((UserDirectory) parent.getHierarchyUnit().getDirectory()).getRoleSimpleName(role); + return new ContentName(name); + } + + @Override + public Content getParent() { + return parent; + } + + @SuppressWarnings("unchecked") + @Override + public A adapt(Class clss) { + if (Role.class.isAssignableFrom(clss)) + return (A) role; + return super.adapt(clss); + } + +} 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 index bfcd0118d..43cae8572 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java @@ -1,66 +1,100 @@ package org.argeo.cms.acr.fs; +import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.UserDefinedFileAttributeView; import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import javax.xml.XMLConstants; import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; import org.argeo.api.acr.ContentResourceException; +import org.argeo.api.acr.CrAttributeType; import org.argeo.api.acr.CrName; -import org.argeo.api.acr.spi.AbstractContent; +import org.argeo.api.acr.DName; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; -import org.argeo.util.FsUtils; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.acr.AbstractContent; +import org.argeo.cms.acr.ContentUtils; +import org.argeo.cms.util.FsUtils; +/** Content persisted as a filesystem {@link Path}. */ public class FsContent extends AbstractContent implements ProvidedContent { - private final static String USER_ = "user:"; + private CmsLog log = CmsLog.getLog(FsContent.class); + + final static String USER_ = "user:"; private static final Map BASIC_KEYS; private static final Map POSIX_KEYS; static { BASIC_KEYS = new HashMap<>(); - BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime"); - BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime"); - BASIC_KEYS.put(CrName.SIZE.get(), "basic:size"); - BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey"); + BASIC_KEYS.put(DName.creationdate.qName(), "basic:creationTime"); + BASIC_KEYS.put(DName.getlastmodified.qName(), "basic:lastModifiedTime"); + BASIC_KEYS.put(DName.getcontentlength.qName(), "basic:size"); + + BASIC_KEYS.put(CrName.fileKey.qName(), "basic:fileKey"); POSIX_KEYS = new HashMap<>(BASIC_KEYS); - POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner"); - POSIX_KEYS.put(CrName.GROUP.get(), "posix:group"); - POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions"); + POSIX_KEYS.put(DName.owner.qName(), "owner:owner"); + POSIX_KEYS.put(DName.group.qName(), "posix:group"); + POSIX_KEYS.put(CrName.permissions.qName(), "posix:permissions"); } - private final ProvidedSession session; private final FsContentProvider provider; private final Path path; - private final boolean isRoot; + private final boolean isMountBase; private final QName name; protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) { - this.session = session; + super(session); this.provider = contentProvider; this.path = path; - this.isRoot = contentProvider.isRoot(path); + this.isMountBase = contentProvider.isMountBase(path); // TODO check file names with ':' ? - if (isRoot) - this.name = CrName.ROOT.get(); - else - this.name = session.parsePrefixedName(path.getFileName().toString()); + if (isMountBase) { + String mountPath = provider.getMountPath(); + if (mountPath != null && !mountPath.equals(ContentUtils.ROOT_SLASH)) { + Content mountPoint = session.getMountPoint(mountPath); + this.name = mountPoint.getName(); + } else { + this.name = CrName.root.qName(); + } + } else { + + // TODO should we support prefixed name for known types? + QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString()); +// QName providerName = new QName(path.getFileName().toString()); + // TODO remove extension if mounted? + this.name = new ContentName(providerName, session); + } } protected FsContent(FsContent context, Path path) { @@ -80,12 +114,32 @@ public class FsContent extends AbstractContent implements ProvidedContent { * ATTRIBUTES */ + @SuppressWarnings("unchecked") @Override public Optional get(QName key, Class clss) { Object value; try { // We need to add user: when accessing via Files#getAttribute - value = Files.getAttribute(path, toFsAttributeKey(key)); + + if (POSIX_KEYS.containsKey(key)) { + value = Files.getAttribute(path, toFsAttributeKey(key)); + } else { + UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, + UserDefinedFileAttributeView.class); + String prefixedName = NamespaceUtils.toPrefixedName(provider, key); + if (!udfav.list().contains(prefixedName)) + return Optional.empty(); + ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName)); + udfav.read(prefixedName, buf); + buf.flip(); + if (buf.hasArray()) + value = buf.array(); + else { + byte[] arr = new byte[buf.remaining()]; + buf.get(arr); + value = arr; + } + } } catch (IOException e) { throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e); } @@ -99,15 +153,41 @@ public class FsContent extends AbstractContent implements ProvidedContent { } // TODO perform trivial file conversion to other formats } + + // TODO better deal with multiple types if (value instanceof byte[]) { - res = (A) new String((byte[]) value, StandardCharsets.UTF_8); - } - if (res == null) - try { - res = (A) value; - } catch (ClassCastException e) { - return Optional.empty(); + String str = new String((byte[]) value, StandardCharsets.UTF_8); + String[] arr = str.split("\n"); + + if (arr.length == 1) { + if (clss.isAssignableFrom(String.class)) { + res = (A) arr[0]; + } else { + res = (A) CrAttributeType.parse(arr[0]); + } + } else { + List lst = new ArrayList<>(); + for (String s : arr) { + lst.add(CrAttributeType.parse(s)); + } + res = (A) lst; } + } + if (res == null) { + if (isDefaultAttrTypeRequested(clss)) + return Optional.of((A) CrAttributeType.parse(value.toString())); + if (clss.isAssignableFrom(value.getClass())) + return Optional.of((A) value); + if (clss.isAssignableFrom(String.class)) + return Optional.of((A) value.toString()); + log.warn("Cannot interpret " + key + " in " + this); + return Optional.empty(); +// try { +// res = (A) value; +// } catch (ClassCastException e) { +// return Optional.empty(); +// } + } return Optional.of(res); } @@ -118,7 +198,11 @@ public class FsContent extends AbstractContent implements ProvidedContent { if (udfav != null) { try { for (String name : udfav.list()) { - result.add(session.parsePrefixedName(name)); + QName providerName = NamespaceUtils.parsePrefixedName(provider, name); + if (providerName.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) + continue; // skip prefix mapping + QName sessionName = new ContentName(providerName, getSession()); + result.add(sessionName); } } catch (IOException e) { throw new ContentResourceException("Cannot list attributes for " + path, e); @@ -131,7 +215,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { protected void removeAttr(QName key) { UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); try { - udfav.delete(session.toPrefixedName(key)); + udfav.delete(NamespaceUtils.toPrefixedName(provider, key)); } catch (IOException e) { throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e); } @@ -140,10 +224,22 @@ public class FsContent extends AbstractContent implements ProvidedContent { @Override public Object put(QName key, Object value) { Object previous = get(key); + + String toWrite; + if (value instanceof List) { + StringJoiner sj = new StringJoiner("\n"); + for (Object obj : (List) value) { + sj.add(obj.toString()); + } + toWrite = sj.toString(); + } else { + toWrite = value.toString(); + } + UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); - ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8)); + ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8)); try { - int size = udfav.write(session.toPrefixedName(key), bb); + udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb); } catch (IOException e) { throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e); } @@ -154,7 +250,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { if (POSIX_KEYS.containsKey(key)) return POSIX_KEYS.get(key); else - return USER_ + session.toPrefixedName(key); + return USER_ + NamespaceUtils.toPrefixedName(provider, key); } /* @@ -164,7 +260,19 @@ public class FsContent extends AbstractContent implements ProvidedContent { public Iterator iterator() { if (Files.isDirectory(path)) { try { - return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator(); + return Files.list(path).map((p) -> { + FsContent fsContent = new FsContent(this, p); + Optional isMount = fsContent.get(CrName.mount.qName(), String.class); + if (isMount.orElse("false").equals("true")) { + QName[] classes = null; + ContentProvider contentProvider = getSession().getRepository() + .getMountContentProvider(fsContent, false, classes); + Content mountedContent = contentProvider.get(getSession(), ""); + return mountedContent; + } else { + return (Content) fsContent; + } + }).iterator(); } catch (IOException e) { throw new ContentResourceException("Cannot list " + path, e); } @@ -175,9 +283,10 @@ public class FsContent extends AbstractContent implements ProvidedContent { @Override public Content add(QName name, QName... classes) { + FsContent fsContent; try { - Path newPath = path.resolve(session.toPrefixedName(name)); - if (ContentName.contains(classes, CrName.COLLECTION.get())) + Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name)); + if (ContentName.contains(classes, DName.collection.qName())) Files.createDirectory(newPath); else Files.createFile(newPath); @@ -185,10 +294,23 @@ public class FsContent extends AbstractContent implements ProvidedContent { // for(ContentClass clss:classes) { // Files.setAttribute(newPath, name, newPath, null) // } - return new FsContent(this, newPath); + fsContent = new FsContent(this, newPath); } catch (IOException e) { throw new ContentResourceException("Cannot create new content", e); } + + if (classes.length > 0) + fsContent.addContentClasses(classes); + if (getSession().getRepository().shouldMount(classes)) { + ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true, + classes); + Content mountedContent = contentProvider.get(getSession(), ""); + fsContent.put(CrName.mount.qName(), "true"); + return mountedContent; + + } else { + return fsContent; + } } @Override @@ -198,22 +320,114 @@ public class FsContent extends AbstractContent implements ProvidedContent { @Override public Content getParent() { - if (isRoot) - return null;// TODO deal with mounts + if (isMountBase) { + String mountPath = provider.getMountPath(); + if (mountPath == null || mountPath.equals("/")) + return null; + String[] parent = ContentUtils.getParentPath(mountPath); + return getSession().get(parent[0]); + } return new FsContent(this, path.getParent()); } + @SuppressWarnings("unchecked") + @Override + public C open(Class clss) throws IOException, IllegalArgumentException { + if (InputStream.class.isAssignableFrom(clss)) { + if (Files.isDirectory(path)) + throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory"); + return (C) Files.newInputStream(path); + } else if (OutputStream.class.isAssignableFrom(clss)) { + if (Files.isDirectory(path)) + throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory"); + return (C) Files.newOutputStream(path); + } + return super.open(clss); + } + /* - * ACCESSORS + * MOUNT MANAGEMENT */ @Override - public ProvidedSession getSession() { - return session; + public ProvidedContent getMountPoint(String relativePath) { + Path childPath = path.resolve(relativePath); + // TODO check that it is a mount + return new FsContent(this, childPath); } + /* + * TYPING + */ + + @Override + public List getContentClasses() { + List res = new ArrayList<>(); + List value = getMultiple(DName.resourcetype.qName(), String.class); + for (String s : value) { + QName name = NamespaceUtils.parsePrefixedName(provider, s); + res.add(name); + } + if (Files.isDirectory(path)) + res.add(DName.collection.qName()); + return res; + } + + @Override + public void addContentClasses(QName... contentClass) { + List toWrite = new ArrayList<>(); + for (QName cc : getContentClasses()) { + if (cc.equals(DName.collection.qName())) + continue; // skip + toWrite.add(NamespaceUtils.toPrefixedName(provider, cc)); + } + for (QName cc : contentClass) { + toWrite.add(NamespaceUtils.toPrefixedName(provider, cc)); + } + put(DName.resourcetype.qName(), toWrite); + } + + /* + * ACCESSORS + */ + @Override public FsContentProvider getProvider() { return provider; } + /* + * READ / WRITE + */ + @SuppressWarnings("unchecked") + public CompletableFuture write(Class clss) { + if (isContentClass(DName.collection.qName())) { + throw new IllegalStateException("Cannot directly write to a collection"); + } + if (InputStream.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture<>(); + res.thenAccept((in) -> { + try { + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException("Cannot write to " + path, e); + } + }); + return (CompletableFuture) res; + } else if (Source.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture(); + res.thenAccept((source) -> { +// Path targetPath = path.getParent().resolve(path.getFileName()+".xml"); + Path targetPath = path; + try (OutputStream out = Files.newOutputStream(targetPath)) { + StreamResult result = new StreamResult(out); + TransformerFactory.newDefaultInstance().newTransformer().transform(source, result); + } catch (IOException | TransformerException e) { + throw new RuntimeException("Cannot write to " + path, e); + } + }); + return (CompletableFuture) res; + } else { + return super.write(clss); + } + } } 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 index 99ed3a8ca..9b1b96683 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java @@ -1,23 +1,105 @@ package org.argeo.cms.acr.fs; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.UserDefinedFileAttributeView; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; -import org.argeo.api.acr.Content; +import org.argeo.api.acr.ArgeoNamespace; import org.argeo.api.acr.ContentResourceException; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.RuntimeNamespaceContext; import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; +/** Access a file system as a {@link ContentProvider}. */ public class FsContentProvider implements ContentProvider { - private final Path rootPath; + final static String XMLNS_ = "xmlns:"; - public FsContentProvider(Path rootPath) { - super(); + protected String mountPath; + protected Path rootPath; + + private NavigableMap prefixes = new TreeMap<>(); + + public FsContentProvider(String mountPath, Path rootPath) { + Objects.requireNonNull(mountPath); + Objects.requireNonNull(rootPath); + + this.mountPath = mountPath; this.rootPath = rootPath; + // FIXME make it more robust + initNamespaces(); + } + + protected FsContentProvider() { + } - boolean isRoot(Path path) { + protected void initNamespaces() { + try { + UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath, + UserDefinedFileAttributeView.class); + if (udfav == null) + return; + for (String name : udfav.list()) { + if (name.startsWith(XMLNS_)) { + ByteBuffer buf = ByteBuffer.allocate(udfav.size(name)); + udfav.read(name, buf); + buf.flip(); + String namespace = StandardCharsets.UTF_8.decode(buf).toString(); + String prefix = name.substring(XMLNS_.length()); + prefixes.put(prefix, namespace); + } + } + + // defaults + addDefaultNamespace(udfav, ArgeoNamespace.CR_DEFAULT_PREFIX, ArgeoNamespace.CR_NAMESPACE_URI); + addDefaultNamespace(udfav, "basic", ArgeoNamespace.CR_NAMESPACE_URI); + addDefaultNamespace(udfav, "owner", ArgeoNamespace.CR_NAMESPACE_URI); + addDefaultNamespace(udfav, "posix", ArgeoNamespace.CR_NAMESPACE_URI); + } catch (IOException e) { + throw new RuntimeException("Cannot read namespaces from " + rootPath, e); + } + + } + + protected void addDefaultNamespace(UserDefinedFileAttributeView udfav, String prefix, String namespace) + throws IOException { + if (!prefixes.containsKey(prefix)) { + ByteBuffer bb = ByteBuffer.wrap(namespace.getBytes(StandardCharsets.UTF_8)); + udfav.write(XMLNS_ + prefix, bb); + prefixes.put(prefix, namespace); + } + } + + public void registerPrefix(String prefix, String namespace) { + if (prefixes.containsKey(prefix)) + prefixes.remove(prefix); + try { + UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath, + UserDefinedFileAttributeView.class); + addDefaultNamespace(udfav, prefix, namespace); + } catch (IOException e) { + throw new RuntimeException("Cannot register namespace " + prefix + " " + namespace + " on " + rootPath, e); + } + + } + + @Override + public String getMountPath() { + return mountPath; + } + + boolean isMountBase(Path path) { try { return Files.isSameFile(rootPath, path); } catch (IOException e) { @@ -26,7 +108,39 @@ public class FsContentProvider implements ContentProvider { } @Override - public Content get(ProvidedSession session, String mountPath, String relativePath) { + public ProvidedContent get(ProvidedSession session, String relativePath) { return new FsContent(session, this, rootPath.resolve(relativePath)); } + + @Override + public boolean exists(ProvidedSession session, String relativePath) { + return Files.exists(rootPath.resolve(relativePath)); + } + + /* + * NAMESPACE CONTEXT + */ + + @Override + public String getNamespaceURI(String prefix) { + return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), prefix); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + Iterator res = NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream() + .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), + namespaceURI); + if (!res.hasNext()) { + String prefix = RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI); + if (prefix != null) { + registerPrefix(prefix, namespaceURI); + return getPrefixes(namespaceURI); + } else { + throw new IllegalArgumentException("Unknown namespace " + namespaceURI); + } + } + return res; + } + } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProviderService.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProviderService.java new file mode 100644 index 000000000..4e986f6f2 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProviderService.java @@ -0,0 +1,39 @@ +package org.argeo.cms.acr.fs; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.Map; +import java.util.Objects; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsState; + +public class FsContentProviderService extends FsContentProvider { + private CmsState cmsState; + + public void start(Map properties) { + mountPath = properties.get(CmsConstants.ACR_MOUNT_PATH); + Objects.requireNonNull(mountPath); + if (!mountPath.startsWith("/")) + throw new IllegalArgumentException("Mount path must start with /"); + + String relPath = mountPath.substring(1); + rootPath = cmsState.getDataPath(relPath); + try { + Files.createDirectories(rootPath); + } catch (IOException e) { + throw new IllegalStateException( + "Cannot initialize FS content provider " + mountPath + " with base" + rootPath, e); + } + + initNamespaces(); + } + + public void stop() { + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/DSMLv2.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/DSMLv2.xsd new file mode 100644 index 000000000..320da743e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/DSMLv2.xsd @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/SVG.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/SVG.xsd new file mode 100644 index 000000000..7cb1689ad --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/SVG.xsd @@ -0,0 +1,2975 @@ + + + + + + + + + + The actual definition is + baseline | sub | super | <percentage> | <length> | inherit + not sure that union can do this + + + + + + + + Space-separated list of classes + + + + + + <shape> | auto | inherit + + + + + + <uri> | none | inherit + + + + + + 'clip-rule' or fill-rule property/attribute value + + + + + + + + + + media type, as per [RFC2045] + media type, as per [RFC2045] + + + + + + a <co-ordinate> + a coordinate, which is a number optionally followed immediately by a unit identifier. Perhaps it is possible to represent this as a union by declaring unit idenifiers as a type? + + + + + + + + a space separated list of CoordinateType. Punt to 'string' for now + + + + + + a CSS2 Color + Color as defined in CSS2 and XSL 1.0 plus additional recognised color keyword names (the 'X11 colors') + + + + + + Value is an optional comma-separated list orf uri references followed by one token from an enumerated list. + + [ [<uri> ,]* [ auto | crosshair | default | pointer | move | e-resize | ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize | w-resize| text | wait | help ] ] | inherit + + + + + + accumulate | new [ <x> <y> <width> <height> ] | inherit + + + + + + extension list specification + + + + + + feature list specification + + + + + + <uri> | none | inherit + + + + + + + [[ <family-name> | <generic-family> ],]* [<family-name> | <generic-family>] | inherit + 'font-family' property/attribute value (i.e., list of fonts) + + + + + + 'font-size' property/attribute value + <absolute-size> | <relative-size> | <length> | <percentage> | inherit + + + + + + 'font-size-adjust' property/attribute value + <number> | none | inherit + + + + + + 'glyph-orientation-horizontal' property/attribute value (e.g., <angle>) + <angle> | inherit + + + + + + 'glyph-orientation-vertical' property/attribute value (e.g., 'auto', <angle>) + auto | <angle> | inherit + + + + + + + 'kerning' property/attribute value (e.g., auto | <length>) + auto | <length> | inherit + + + + + + a language code, as per [RFC3066] + + + + + + + a comma-separated list of language codes, as per [RFC3066] + + + + + + + a <length> + + + + + + + + a list of <length>s + + + + + + + link to this target + + + + + + 'marker' property/attribute value (e.g., 'none', %URI;) + + + + + + + 'mask' property/attribute value (e.g., 'none', %URI;) + <uri> | none | inherit + + + + + + comma-separated list of media descriptors. + + + + + + + list of <number>s, but at least one and at most two + + + + + + a <number> or a <percentage> + + + + + + list of <number>s + + + + + + opacity value (e.g., <number>) + <alphavalue> | inherit + + + + + + a 'fill' or 'stroke' property/attribute value + + + + + + a path data specification + + Yes, of course this was generated by a program! + + + + + + a list of points + + + + + + 'preserveAspectRatio' attribute specification + + + + + + + + + script expression + + + + + + 'letter-spacing' or 'word-spacing' property/attribute value (e.g., normal | <length>) + + + + + + 'stroke-dasharray' property/attribute value (e.g., 'none', list of <number>s) + + + + + + 'stroke-dashoffset' property/attribute value (e.g., 'none', >length>) + + + + + + 'stroke-miterlimit' property/attribute value (e.g., <number>) + + + + + + 'stroke-width' property/attribute value (e.g., <length>) + + + + + + + style sheet data + + + + + + An SVG color value (sRGB plus optional ICC) + + + + + + + 'text-decoration' property/attribute value (e.g., 'none', 'underline') + + + + + + Yes, of course this was generated by a program! + list of transforms + + + + + + + 'viewBox' attribute specification + + + + + + All elements have an ID + + + + + + + + Common attributes for elements that might contain character data content + + + + + + + Common attributes to check for system capabilities + + + + + + + + For most uses of URI referencing: standard XLink attributes other than xlink:href + + + + + + + + + + + Standard XLink attributes for uses of URI referencing where xlink:show is 'embed' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following presentation attributes have to do with specifying color. + + + + + + + + + + + + + + + + + + + + + + + + + + The following presentation attributes apply to container elements + + + + + + The following presentation attributes apply to 'feFlood' elements + + + + + + + The following presentation attributes apply to filter primitives + + + + + + + + + + + + + + + The following presentation attributes apply to filling and stroking operations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following presentation attributes have to do with selecting a font to use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following presentation attributes apply to gradient 'stop' elements + + + + + + + The following presentation attributes apply to graphics elements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following presentation attributes apply to 'image' elements + + + + + + The following presentation attributes apply to 'feDiffuseLighting' and 'feSpecularLighting' elements + + + + + + The following presentation attributes apply to marker operations + + + + + + + + The following presentation attributes apply to text content elements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following presentation attributes apply to 'text' elements + + + + + + + + + + + + + + + + + + The following presentation attributes apply to elements that establish viewports + + + + + + + + + + + + + + + + + The following represents the complete list of presentation attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A bit simpler than the DTD, but see commented-out alternative + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.dtd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.dtd new file mode 100644 index 000000000..e8e8f7625 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.dtd @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%xs-datatypes; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.xsd new file mode 100644 index 000000000..12c220911 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.xsd @@ -0,0 +1,2534 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]> + + + + Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp + Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp + + + + + + The schema corresponding to this document is normative, + with respect to the syntactic constraints it expresses in the + XML Schema language. The documentation (within <documentation> elements) + below, is not normative, but rather highlights important aspects of + the W3C Recommendation of which this is a part + + + + + The simpleType element and all of its members are defined + towards the end of this schema document + + + + + + Get access to the xml: attribute groups for xml:lang + as declared on 'schema' and 'documentation' below + + + + + + + + This type is extended by almost all schema types + to allow attributes from other namespaces to be + added to user schemas. + + + + + + + + + + + + + This type is extended by all types which allow annotation + other than <schema> itself + + + + + + + + + + + + + + + + This group is for the + elements which occur freely at the top level of schemas. + All of their types are based on the "annotated" type by extension. + + + + + + + + + + + + + This group is for the + elements which can self-redefine (see <redefine> below). + + + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + + A utility type, not for public use + + #all or (possibly empty) subset of {extension, restriction} + + + + + + + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + + + + A utility type, not for public use + + #all or (possibly empty) subset of {extension, restriction, list, union} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for maxOccurs + + + + + + + + + + + + for all particles + + + + + + + for element, group and attributeGroup, + which both define and reference + + + + + + + + 'complexType' uses this + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This branch is short for + <complexContent> + <restriction base="xs:anyType"> + ... + </restriction> + </complexContent> + + + + + + + + + + + + + + + Will be restricted to required or forbidden + + + + + + Not allowed if simpleContent child is chosen. + May be overriden by setting on complexContent child. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This choice is added simply to + make this a valid restriction per the REC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Overrides any setting on complexType parent. + + + + + + + + + + + + + + + This choice is added simply to + make this a valid restriction per the REC + + + + + + + + + + + + + + + + + No typeDefParticle group reference + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A utility type, not for public use + + #all or (possibly empty) subset of {substitution, extension, + restriction} + + + + + + + + + + + + + + + + + + + + + + + + + The element element can be used either + at the top level to define an element-type binding globally, + or within a content model to either reference a globally-defined + element or type or declare an element-type binding locally. + The ref form is not allowed at the top level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + group type for explicit groups, named top-level groups and + group references + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + group type for the three kinds of group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This choice with min/max is here to + avoid a pblm with the Elt:All/Choice/Seq + Particle derivation constraint + + + + + + + + + + restricted max/min + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only elements allowed inside + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple type for the value of the 'namespace' attr of + 'any' and 'anyAttribute' + + + + Value is + ##any - - any non-conflicting WFXML/attribute at all + + ##other - - any non-conflicting WFXML/attribute from + namespace other than targetNS + + ##local - - any unqualified non-conflicting WFXML/attribute + + one or - - any non-conflicting WFXML/attribute from + more URI the listed namespaces + references + (space separated) + + ##targetNamespace or ##local may appear in the above list, to + refer to the targetNamespace of the enclosing + schema or an absent targetNamespace respectively + + + + + + A utility type, not for public use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A subset of XPath expressions for use +in selectors + A utility type, not for public +use + + + + The following pattern is intended to allow XPath + expressions per the following EBNF: + Selector ::= Path ( '|' Path )* + Path ::= ('.//')? Step ( '/' Step )* + Step ::= '.' | NameTest + NameTest ::= QName | '*' | NCName ':' '*' + child:: is also allowed + + + + + + + + + + + + + + + + + + + + + + + A subset of XPath expressions for use +in fields + A utility type, not for public +use + + + + The following pattern is intended to allow XPath + expressions per the same EBNF as for selector, + with the following change: + Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest ) + + + + + + + + + + + + + + + + + + + + + + + + + + + The three kinds of identity constraints, all with + type of or derived from 'keybase'. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A utility type, not for public use + + A public identifier, per ISO 8879 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + notations for use within XML Schema schemas + + + + + + + + + Not the real urType, but as close an approximation as we can + get in the XML representation + + + + + + + + + + First the built-in primitive datatypes. These definitions are for + information only, the real built-in definitions are magic. + + + + For each built-in datatype in this schema (both primitive and + derived) can be uniquely addressed via a URI constructed + as follows: + 1) the base URI is the URI of the XML Schema namespace + 2) the fragment identifier is the name of the datatype + + For example, to address the int datatype, the URI is: + + http://www.w3.org/2001/XMLSchema#int + + Additionally, each facet definition element can be uniquely + addressed via a URI constructed as follows: + 1) the base URI is the URI of the XML Schema namespace + 2) the fragment identifier is the name of the facet + + For example, to address the maxInclusive facet, the URI is: + + http://www.w3.org/2001/XMLSchema#maxInclusive + + Additionally, each facet usage in a built-in datatype definition + can be uniquely addressed via a URI constructed as follows: + 1) the base URI is the URI of the XML Schema namespace + 2) the fragment identifier is the name of the datatype, followed + by a period (".") followed by the name of the facet + + For example, to address the usage of the maxInclusive facet in + the definition of int, the URI is: + + http://www.w3.org/2001/XMLSchema#int.maxInclusive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOTATION cannot be used directly in a schema; rather a type + must be derived from it by specifying at least one enumeration + facet whose value is the name of a NOTATION declared in the + schema. + + + + + + + + + + Now the derived primitive types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pattern specifies the content of section 2.12 of XML 1.0e2 + and RFC 3066 (Revised version of RFC 1766). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pattern matches production 7 from the XML spec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pattern matches production 5 from the XML spec + + + + + + + + + + + + + + + pattern matches production 4 from the Namespaces in XML spec + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A utility type, not for public use + + + + + + + + + + + + + + + + + + + + + + #all or (possibly empty) subset of {restriction, union, list} + + + A utility type, not for public use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can be restricted to required or forbidden + + + + + + + + + + + + + + + + + + Required at the top level + + + + + + + + + + + + + + + + + + + Forbidden when nested + + + + + + + + + + + + + + + + + + + We should use a substitution group for facets, but + that's ruled out because it would allow users to + add their own, which we're not ready for yet. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + base attribute and simpleType child are mutually + exclusive, but one or other is required + + + + + + + + + + + + + + + + itemType attribute and simpleType child are mutually + exclusive, but one or other is required + + + + + + + + + + + + + + + + + + memberTypes attribute must be non-empty or there must be + at least one simpleType child + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/cr.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/cr.xsd new file mode 100644 index 000000000..11ab59a8b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/cr.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + An UUID as defined in RFC4122. + + + + + + + + + + + The URN of an UUID as defined in RFC4122. + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/datatypes.dtd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/datatypes.dtd new file mode 100644 index 000000000..8e48553be --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/datatypes.dtd @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/schema-for-xslt20.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/schema-for-xslt20.xsd new file mode 100644 index 000000000..30b1c5a07 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/schema-for-xslt20.xsd @@ -0,0 +1,1134 @@ + + + + + + + + This is a schema for XSLT 2.0 stylesheets. + + It defines all the elements that appear in the XSLT namespace; it also + provides hooks that allow the inclusion of user-defined literal result elements, + extension instructions, and top-level data elements. + + The schema is derived (with kind permission) from a schema for XSLT 1.0 stylesheets + produced by Asir S Vedamuthu of WebMethods Inc. + + This schema is available for use under the conditions of the W3C Software License + published at http://www.w3.org/Consortium/Legal/copyright-software-19980720 + + The schema is organized as follows: + + PART A: definitions of complex types and model groups used as the basis + for element definitions + PART B: definitions of individual XSLT elements + PART C: definitions for literal result elements + PART D: definitions of simple types used in attribute definitions + + This schema does not attempt to define all the constraints that apply to a valid + XSLT 2.0 stylesheet module. It is the intention that all valid stylesheet modules + should conform to this schema; however, the schema is non-normative and in the event + of any conflict, the text of the Recommendation takes precedence. + + This schema does not implement the special rules that apply when a stylesheet + has sections that use forwards-compatible-mode. In this mode, setting version="3.0" + allows elements from the XSLT namespace to be used that are not defined in XSLT 2.0. + + Simplified stylesheets (those with a literal result element as the outermost element) + will validate against this schema only if validation starts in lax mode. + + This version is dated 2007-03-16 + Authors: Michael H Kay, Saxonica Limited + Jeni Tennison, Jeni Tennison Consulting Ltd. + + 2007-03-15: added xsl:document element + revised xsl:sequence element + see http://www.w3.org/Bugs/Public/show_bug.cgi?id=4237 + + + + + + + + + + + + + + + + + PART A: definitions of complex types and model groups used as the basis + for element definitions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PART B: definitions of individual XSLT elements + Elements are listed in alphabetical order. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PART C: definition of literal result elements + + There are three ways to define the literal result elements + permissible in a stylesheet. + + (a) do nothing. This allows any element to be used as a literal + result element, provided it is not in the XSLT namespace + + (b) declare all permitted literal result elements as members + of the xsl:literal-result-element substitution group + + (c) redefine the model group xsl:result-elements to accommodate + all permitted literal result elements. + + Literal result elements are allowed to take certain attributes + in the XSLT namespace. These are defined in the attribute group + literal-result-element-attributes, which can be included in the + definition of any literal result element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PART D: definitions of simple types used in stylesheet attributes + + + + + + + + This type is used for all attributes that allow an attribute value template. + The general rules for the syntax of attribute value templates, and the specific + rules for each such attribute, are described in the XSLT 2.0 Recommendation. + + + + + + + + + A string containing exactly one character. + + + + + + + + + + + An XPath 2.0 expression. + + + + + + + + + + + Describes how type annotations in source documents are handled. + + + + + + + + + + + + + The level attribute of xsl:number: + one of single, multiple, or any. + + + + + + + + + + + + + The mode attribute of xsl:apply-templates: + either a QName, or #current, or #default. + + + + + + + + + + + + + + + + The mode attribute of xsl:template: + either a list, each member being either a QName or #default; + or the value #all + + + + + + + + + + + + + + + + + + + + + + + + + + + + A list of NameTests, as defined in the XPath 2.0 Recommendation. + Each NameTest is either a QName, or "*", or "prefix:*", or "*:localname" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The method attribute of xsl:output: + Either one of the recognized names "xml", "xhtml", "html", "text", + or a QName that must include a prefix. + + + + + + + + + + + + + + + + + + + + + + + A match pattern as defined in the XSLT 2.0 Recommendation. + The syntax for patterns is a restricted form of the syntax for + XPath 2.0 expressions. + + + + + + + + + Either a namespace prefix, or #default. + Used in the xsl:namespace-alias element. + + + + + + + + + + + + + + + A list of QNames. + Used in the [xsl:]use-attribute-sets attribute of various elements, + and in the cdata-section-elements attribute of xsl:output + + + + + + + + + A QName. + This schema does not use the built-in type xs:QName, but rather defines its own + QName type. Although xs:QName would define the correct validation on these attributes, + a schema processor would expand unprefixed QNames incorrectly when constructing the PSVI, + because (as defined in XML Schema errata) an unprefixed xs:QName is assumed to be in + the default namespace, which is not the correct assumption for XSLT. + The data type is defined as a restriction of the built-in type Name, restricted + so that it can only contain one colon which must not be the first or last character. + + + + + + + + + + + The description of a data type, conforming to the + SequenceType production defined in the XPath 2.0 Recommendation + + + + + + + + + + + + + + + Describes different ways of type-annotating an element or attribute. + + + + + + + + + + + + Describes different ways of type-annotating an element or attribute. + + + + + + + + + + + + + + One of the values "yes" or "no". + + + + + + + + + + + + One of the values "yes" or "no" or "omit". + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd new file mode 100644 index 000000000..e7443f718 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd new file mode 100644 index 000000000..199a469bd --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd @@ -0,0 +1,280 @@ + + + + + This schema document provides attribute declarations and +attribute group, complex type and simple type definitions which can be used in +the construction of user schemas to define the structure of particular linking +constructs, e.g. + + + + + + + ... + + ... + + + ... +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Intended for use as the type of user-declared elements to make them + simple links. + + + + + + + + + + + + + + + + + + + + + + + + + Intended for use as the type of user-declared elements to make them + extended links. + Note that the elements referenced in the content model are all abstract. + The intention is that by simply declaring elements with these as their + substitutionGroup, all the right things will happen. + + + + + + + + + + + + + + xml:lang is not required, but provides much of the + motivation for title elements in addition to attributes, and so + is provided here for convenience. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + label is not required, but locators have no particular + XLink function if they are not labeled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from and to have default behavior when values are missing + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml.xsd new file mode 100644 index 000000000..aea7d0db0 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml.xsd @@ -0,0 +1,287 @@ + + + + + + +
    +

    About the XML namespace

    + +
    +

    + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

    +

    + See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

    +

    + Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

    +

    + See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

    +
    +
    + + + + + + +
    + +

    lang (as an attribute name)

    +

    + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

    + +
    +
    +

    Notes

    +

    + Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

    +

    + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

    +

    + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

    +
    +
    +
    + + + + + + + + + +
    + + + + +
    + +

    space (as an attribute name)

    +

    + denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

    + +
    +
    +
    + + + + + + +
    + + + +
    + +

    base (as an attribute name)

    +

    + denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

    + +

    + See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

    +
    +
    +
    +
    + + + + +
    + +

    id (as an attribute name)

    +

    + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

    + +

    + See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

    +
    +
    +
    +
    + + + + + + + + + + +
    + +

    Father (in any context at all)

    + +
    +

    + denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

    +
    +

    + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

    +
    +
    +
    +
    +
    + + + +
    +

    About this schema document

    + +
    +

    + This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

    +

    + To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

    +
    +          <schema . . .>
    +           . . .
    +           <import namespace="http://www.w3.org/XML/1998/namespace"
    +                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
    +     
    +

    + or +

    +
    +           <import namespace="http://www.w3.org/XML/1998/namespace"
    +                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
    +     
    +

    + Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

    +
    +          <type . . .>
    +           . . .
    +           <attributeGroup ref="xml:specialAttrs"/>
    +     
    +

    + will define a type which will schema-validate an instance element + with any of those attributes. +

    +
    +
    +
    +
    + + + +
    +

    Versioning policy for this schema document

    +
    +

    + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

    +

    + At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

    +

    + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

    +

    + Previous dated (and unchanging) versions of this schema + document are at: +

    + +
    +
    +
    +
    + + + 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 index 626f582e5..a4c14186a 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -1,30 +1,55 @@ package org.argeo.cms.acr.xml; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.nio.CharBuffer; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ForkJoinPool; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; -import org.argeo.api.acr.spi.AbstractContent; +import org.argeo.api.acr.CrName; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.AbstractContent; +import org.argeo.cms.acr.ContentUtils; import org.w3c.dom.Attr; +import org.w3c.dom.DOMException; import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; +/** Content persisted as a DOM element. */ public class DomContent extends AbstractContent implements ProvidedContent { - private final ProvidedSession session; private final DomContentProvider provider; private final Element element; @@ -32,7 +57,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { private Boolean hasText = null; public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) { - this.session = session; + super(session); this.provider = contentProvider; this.element = element; } @@ -43,9 +68,24 @@ public class DomContent extends AbstractContent implements ProvidedContent { @Override public QName getName() { + if (isLocalRoot()) {// root + String mountPath = provider.getMountPath(); + if (mountPath != null) { + if (ContentUtils.ROOT_SLASH.equals(mountPath)) { + return CrName.root.qName(); + } + Content mountPoint = getSession().getMountPoint(mountPath); + QName mountPointName = mountPoint.getName(); + return mountPointName; + } + } return toQName(this.element); } + protected boolean isLocalRoot() { + return element.getParentNode() == null || element.getParentNode() instanceof Document; + } + protected QName toQName(Node node) { String prefix = node.getPrefix(); if (prefix == null) { @@ -55,27 +95,27 @@ public class DomContent extends AbstractContent implements ProvidedContent { if (namespaceURI == null) { return toQName(node, node.getLocalName()); } else { - String contextPrefix = session.getPrefix(namespaceURI); + String contextPrefix = provider.getPrefix(namespaceURI); if (contextPrefix == null) throw new IllegalStateException("Namespace " + namespaceURI + " is unbound"); - return toQName(node, namespaceURI, node.getLocalName(), session); + return toQName(node, namespaceURI, node.getLocalName(), provider); } } else { String namespaceURI = node.getNamespaceURI(); if (namespaceURI == null) namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix); if (namespaceURI == null) { - namespaceURI = session.getNamespaceURI(prefix); + namespaceURI = provider.getNamespaceURI(prefix); if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) throw new IllegalStateException("Prefix " + prefix + " is unbound"); // TODO bind the prefix in the document? } - return toQName(node, namespaceURI, node.getLocalName(), session); + return toQName(node, namespaceURI, node.getLocalName(), provider); } } protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) { - return new ContentName(namespaceURI, localName, session); + return new ContentName(namespaceURI, localName, namespaceContext); } protected QName toQName(Node source, String localName) { @@ -93,11 +133,14 @@ public class DomContent extends AbstractContent implements ProvidedContent { for (int i = 0; i < attributes.getLength(); i++) { Attr attr = (Attr) attributes.item(i); QName key = toQName(attr); + if (key.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) + continue;// skip prefix mapping result.add(key); } return result; } + @SuppressWarnings("unchecked") @Override public Optional get(QName key, Class clss) { String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null @@ -109,7 +152,7 @@ public class DomContent extends AbstractContent implements ProvidedContent { else return Optional.empty(); } else - return null; + return Optional.empty(); } @Override @@ -117,13 +160,30 @@ public class DomContent extends AbstractContent implements ProvidedContent { Object previous = get(key); String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null : key.getNamespaceURI(); + String prefixToUse = registerPrefixIfNeeded(key); element.setAttributeNS(namespaceUriOrNull, - namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(), + namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(), value.toString()); return previous; } - - + + protected String registerPrefixIfNeeded(QName name) { + String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null + : name.getNamespaceURI(); + String prefixToUse; + if (namespaceUriOrNull != null) { + String registeredPrefix = provider.getPrefix(namespaceUriOrNull); + if (registeredPrefix != null) { + prefixToUse = registeredPrefix; + } else { + provider.registerPrefix(name.getPrefix(), namespaceUriOrNull); + prefixToUse = name.getPrefix(); + } + } else { + prefixToUse = null; + } + return prefixToUse; + } @Override public boolean hasText() { @@ -169,17 +229,27 @@ public class DomContent extends AbstractContent implements ProvidedContent { @Override public Iterator iterator() { NodeList nodeList = element.getChildNodes(); - return new ElementIterator(session, provider, nodeList); + return new ElementIterator(this, getSession(), provider, nodeList); } @Override public Content getParent() { - Node parent = element.getParentNode(); - if (parent == null) - return null; - if (!(parent instanceof Element)) + Node parentNode = element.getParentNode(); + if (isLocalRoot()) { + String mountPath = provider.getMountPath(); + if (mountPath == null) + return null; + if (ContentUtils.ROOT_SLASH.equals(mountPath)) { + return null; + } + String[] parent = ContentUtils.getParentPath(mountPath); + if (ContentUtils.EMPTY.equals(parent[0])) + return null; + return getSession().get(parent[0]); + } + if (!(parentNode instanceof Element)) throw new IllegalStateException("Parent is not an element"); - return new DomContent(this, (Element) parent); + return new DomContent(this, (Element) parentNode); } @Override @@ -188,8 +258,9 @@ public class DomContent extends AbstractContent implements ProvidedContent { Document document = this.element.getOwnerDocument(); String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null : name.getNamespaceURI(); + String prefixToUse = registerPrefixIfNeeded(name); Element child = document.createElementNS(namespaceUriOrNull, - namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart()); + namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart()); element.appendChild(child); return new DomContent(this, child); } @@ -210,10 +281,149 @@ public class DomContent extends AbstractContent implements ProvidedContent { } - public ProvidedSession getSession() { - return session; + @SuppressWarnings("unchecked") + @Override + public A adapt(Class clss) throws IllegalArgumentException { + if (CharBuffer.class.isAssignableFrom(clss)) { + String textContent = element.getTextContent(); + CharBuffer buf = CharBuffer.wrap(textContent); + return (A) buf; + } else if (Source.class.isAssignableFrom(clss)) { + DOMSource source = new DOMSource(element); + return (A) source; + } + return super.adapt(clss); + } + + @SuppressWarnings("unchecked") + public CompletableFuture write(Class clss) { + if (String.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture<>(); + res.thenAccept((s) -> { + getSession().notifyModification(this); + element.setTextContent(s); + }); + return (CompletableFuture) res; + } else if (Source.class.isAssignableFrom(clss)) { + CompletableFuture res = new CompletableFuture<>(); + res.thenAccept((source) -> { + try { + Transformer transformer = provider.getTransformerFactory().newTransformer(); + DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment(); + DOMResult result = new DOMResult(documentFragment); + transformer.transform(source, result); + // Node parentNode = element.getParentNode(); + Element resultElement = (Element) documentFragment.getFirstChild(); + QName resultName = toQName(resultElement); + if (!resultName.equals(getName())) + throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName()); + + // attributes + NamedNodeMap attrs = resultElement.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) { + Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true); + element.getAttributes().setNamedItem(attr2); + } + + // Move all the children + while (element.hasChildNodes()) { + element.removeChild(element.getFirstChild()); + } + while (resultElement.hasChildNodes()) { + element.appendChild(resultElement.getFirstChild()); + } +// parentNode.replaceChild(resultNode, element); +// element = (Element)resultNode; + + } catch (DOMException | TransformerException e) { + throw new RuntimeException("Cannot write to element", e); + } + }); + return (CompletableFuture) res; + } + return super.write(clss); } + @SuppressWarnings("unchecked") + @Override + public C open(Class clss) throws IOException, IllegalArgumentException { + if (InputStream.class.isAssignableFrom(clss)) { + PipedOutputStream out = new PipedOutputStream(); + ForkJoinPool.commonPool().execute(() -> { + try { + Source source = new DOMSource(element); + Result result = new StreamResult(out); + provider.getTransformerFactory().newTransformer().transform(source, result); + out.flush(); + out.close(); + } catch (TransformerException | IOException e) { + throw new RuntimeException("Cannot read " + getPath(), e); + } + }); + return (C) new PipedInputStream(out); + } + return super.open(clss); + } + + @Override + public int getSiblingIndex() { + Node curr = element.getPreviousSibling(); + int count = 1; + while (curr != null) { + if (curr instanceof Element) { + if (Objects.equals(curr.getNamespaceURI(), element.getNamespaceURI()) + && Objects.equals(curr.getLocalName(), element.getLocalName())) { + count++; + } + } + curr = curr.getPreviousSibling(); + } + return count; + } + + /* + * TYPING + */ + @Override + public List getContentClasses() { + List res = new ArrayList<>(); + if (isLocalRoot()) { + String mountPath = provider.getMountPath(); + if (mountPath != null) { + Content mountPoint = getSession().getMountPoint(mountPath); + res.addAll(mountPoint.getContentClasses()); + } + } else { + res.add(getName()); + } + return res; + } + + @Override + public void addContentClasses(QName... contentClass) { + if (isLocalRoot()) { + String mountPath = provider.getMountPath(); + if (mountPath != null) { + Content mountPoint = getSession().getMountPoint(mountPath); + mountPoint.addContentClasses(contentClass); + } + } else { + super.addContentClasses(contentClass); + } + } + + /* + * MOUNT MANAGEMENT + */ + @Override + public ProvidedContent getMountPoint(String relativePath) { + // FIXME use qualified names + Element childElement = (Element) element.getElementsByTagName(relativePath).item(0); + // TODO check that it is a mount + return new DomContent(this, childElement); + } + + @Override public DomContentProvider getProvider() { return provider; } 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 index c5fde8d7c..66ff878d5 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java @@ -1,11 +1,14 @@ package org.argeo.cms.acr.xml; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.xml.namespace.NamespaceContext; +import javax.xml.transform.TransformerFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; @@ -13,29 +16,41 @@ import javax.xml.xpath.XPathFactory; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentNotFoundException; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.CmsContentRepository; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; public class DomContentProvider implements ContentProvider, NamespaceContext { - private Document document; + private final Document document; // XPath // TODO centralise in some executor? private final ThreadLocal xPath; - public DomContentProvider(Document document) { + private TransformerFactory transformerFactory; + + private String mountPath; + + public DomContentProvider(String mountPath, Document document) { + this.mountPath = mountPath; this.document = document; this.document.normalizeDocument(); + + transformerFactory = TransformerFactory.newInstance(); + XPathFactory xPathFactory = XPathFactory.newInstance(); xPath = new ThreadLocal<>() { @Override protected XPath initialValue() { // TODO set the document as namespace context? - XPath res= xPathFactory.newXPath(); + XPath res = xPathFactory.newXPath(); res.setNamespaceContext(DomContentProvider.this); return res; } @@ -53,27 +68,62 @@ public class DomContentProvider implements ContentProvider, NamespaceContext { // } @Override - public Content get(ProvidedSession session, String mountPath, String relativePath) { + public ProvidedContent get(ProvidedSession session, String relativePath) { if ("".equals(relativePath)) return new DomContent(session, this, document.getDocumentElement()); + + NodeList nodes = findContent(relativePath); + if (nodes.getLength() > 1) + throw new IllegalArgumentException("Multiple content found for " + relativePath + " under " + mountPath); + if (nodes.getLength() == 0) + throw new ContentNotFoundException(session, mountPath + "/" + relativePath, + "Path " + relativePath + " under " + mountPath + " was not found"); + Element element = (Element) nodes.item(0); + return new DomContent(session, this, element); + } + + protected NodeList findContent(String relativePath) { if (relativePath.startsWith("/")) throw new IllegalArgumentException("Relative path cannot start with /"); - String xPathExpression = '/' + relativePath; if ("/".equals(mountPath)) - xPathExpression = "/cr:root" + xPathExpression; + xPathExpression = "/" + CrName.root.qName() + xPathExpression; try { NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET); - if (nodes.getLength() > 1) - throw new IllegalArgumentException( - "Multiple content found for " + relativePath + " under " + mountPath); - if (nodes.getLength() == 0) - throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found"); - Element element = (Element) nodes.item(0); - return new DomContent(session, this, element); + return nodes; } catch (XPathExpressionException e) { throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e); } + + } + + @Override + public boolean exists(ProvidedSession session, String relativePath) { + if ("".equals(relativePath)) + return true; + NodeList nodes = findContent(relativePath); + return nodes.getLength() != 0; + } + + public void persist(ProvidedSession session) { + if (mountPath != null) { + Content mountPoint = session.getMountPoint(mountPath); + try (OutputStream out = mountPoint.open(OutputStream.class)) { + CmsContentRepository contentRepository = (CmsContentRepository) session.getRepository(); + contentRepository.writeDom(document, out); + } catch (IOException e) { + throw new IllegalStateException("Cannot persist " + mountPath, e); + } + } + } + + @Override + public String getMountPath() { + return mountPath; + } + + public void registerPrefix(String prefix, String namespace) { + DomUtils.addNamespace(document.getDocumentElement(), prefix, namespace); } /* @@ -81,11 +131,17 @@ public class DomContentProvider implements ContentProvider, NamespaceContext { */ @Override public String getNamespaceURI(String prefix) { + String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix); + if (namespaceURI != null) + return namespaceURI; return document.lookupNamespaceURI(prefix); } @Override public String getPrefix(String namespaceURI) { + String prefix = NamespaceUtils.getStandardPrefix(namespaceURI); + if (prefix != null) + return prefix; return document.lookupPrefix(namespaceURI); } @@ -96,4 +152,8 @@ public class DomContentProvider implements ContentProvider, NamespaceContext { return Collections.unmodifiableList(res).iterator(); } + TransformerFactory getTransformerFactory() { + return transformerFactory; + } + } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java new file mode 100644 index 000000000..3b5ff0dbc --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java @@ -0,0 +1,31 @@ +package org.argeo.cms.acr.xml; + +import javax.xml.XMLConstants; + +import org.w3c.dom.Element; + +public class DomUtils { + public static void addNamespace(Element element, String prefix, String namespace) { + element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix, + namespace); + } + +// public static void writeDom(TransformerFactory transformerFactory, Document document, OutputStream out) +// throws IOException { +// try { +// Transformer transformer = transformerFactory.newTransformer(); +// transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); +// transformer.setOutputProperty(OutputKeys.INDENT, "yes"); +// DOMSource source = new DOMSource(document); +// StreamResult result = new StreamResult(out); +// transformer.transform(source, result); +// } catch (TransformerException e) { +// throw new IOException("Cannot write dom", e); +// } +// } + + /** singleton */ + private DomUtils() { + + } +} 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 index 3b07081e4..3f7476221 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java @@ -3,13 +3,16 @@ package org.argeo.cms.acr.xml; import java.util.Iterator; import java.util.NoSuchElementException; +import org.argeo.api.acr.ArgeoNamespace; import org.argeo.api.acr.Content; +import org.argeo.api.acr.CrName; import org.argeo.api.acr.spi.ProvidedSession; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; class ElementIterator implements Iterator { + private DomContent parent; private final ProvidedSession session; private final DomContentProvider provider; private final NodeList nodeList; @@ -18,7 +21,8 @@ class ElementIterator implements Iterator { private final int length; private Element nextElement = null; - public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) { + public ElementIterator(DomContent parent, ProvidedSession session, DomContentProvider provider, NodeList nodeList) { + this.parent = parent; this.session = session; this.provider = provider; this.nodeList = nodeList; @@ -48,7 +52,15 @@ class ElementIterator implements Iterator { public Content next() { if (nextElement == null) throw new NoSuchElementException(); - DomContent result = new DomContent(session, provider, nextElement); + Content result; + String isMount = nextElement.getAttributeNS(ArgeoNamespace.CR_NAMESPACE_URI, CrName.mount.qName().getLocalPart()); + if (isMount.equals("true")) { + result = session.get(parent.getPath() + '/' + nextElement.getTagName()); + } + + else { + result = new DomContent(session, provider, nextElement); + } currentIndex++; nextElement = findNext(); return result; diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java new file mode 100644 index 000000000..42923997d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java @@ -0,0 +1,128 @@ +package org.argeo.cms.acr.xml; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Consistently normalises an XML in order to ease diff (typically in a + * versioning system). + */ +public class XmlNormalizer { +// private final static Logger logger = System.getLogger(XmlNormalizer.class.getName()); + + private DocumentBuilder documentBuilder; + private TransformerFactory transformerFactory; + + private DOMSource stripSpaceXsl; + + public XmlNormalizer() { + this(2); + } + + public XmlNormalizer(int indent) { + try { + documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder(); + transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", indent); + try (InputStream in = XmlNormalizer.class.getResourceAsStream("stripSpaces.xsl")) { + DOMResult result = new DOMResult(); + transformerFactory.newTransformer().transform(new StreamSource(in), result); + stripSpaceXsl = new DOMSource(result.getNode()); + } + } catch (ParserConfigurationException | TransformerFactoryConfigurationError | TransformerException + | IOException e) { + throw new IllegalStateException("Cannot initialise document builder and transformer", e); + } + } + + public void normalizeXmlFiles(DirectoryStream ds) throws IOException { + for (Path path : ds) { + normalizeXmlFile(path); + } + } + + public void normalizeXmlFile(Path path) throws IOException { + byte[] bytes = Files.readAllBytes(path); + try (ByteArrayInputStream in = new ByteArrayInputStream(bytes); + OutputStream out = Files.newOutputStream(path)) { + normalizeAndIndent(in, out); +// logger.log(Level.DEBUG, () -> "Normalized XML " + path); + } + } + + public void normalizeAndIndent(Source source, Result result) { + try { + Transformer transformer = transformerFactory.newTransformer(stripSpaceXsl); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + // transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + + transformer.transform(source, result); + } catch (TransformerException e) { + throw new RuntimeException("Cannot strip space from " + source, e); + } + } + + public void normalizeAndIndent(InputStream in, OutputStream out) throws IOException { + try { + Document document = documentBuilder.parse(in); + + // clear whitespaces outside tags + document.normalize(); +// XPath xPath = XPathFactory.newInstance().newXPath(); +// NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", document, +// XPathConstants.NODESET); +// +// for (int i = 0; i < nodeList.getLength(); ++i) { +// Node node = nodeList.item(i); +// node.getParentNode().removeChild(node); +// } + + normalizeAndIndent(new DOMSource(document), new StreamResult(out)); + } catch (DOMException | IllegalArgumentException | SAXException | TransformerFactoryConfigurationError e) { + throw new RuntimeException("Cannot normalise and indent XML", e); + } + } + + public static void print(Source source, int indent) { + XmlNormalizer xmlNormalizer = new XmlNormalizer(indent); + xmlNormalizer.normalizeAndIndent(source, new StreamResult(System.out)); + } + + public static void print(Source source) { + print(source, 2); + } + + public static void main(String[] args) throws IOException { + Path dir = Paths.get(args[0]); + XmlNormalizer xmlNormalizer = new XmlNormalizer(); + DirectoryStream ds = Files.newDirectoryStream(dir, "*.svg"); + xmlNormalizer.normalizeXmlFiles(ds); + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/stripSpaces.xsl b/org.argeo.cms/src/org/argeo/cms/acr/xml/stripSpaces.xsl new file mode 100644 index 000000000..c1d265f6d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/stripSpaces.xsl @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java index de3a30270..82873ad01 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java @@ -9,8 +9,7 @@ import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import org.argeo.api.cms.CmsLog; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; +import org.argeo.cms.internal.runtime.CmsContextImpl; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.UserAdmin; @@ -22,7 +21,7 @@ public class AnonymousLoginModule implements LoginModule { private Map sharedState = null; // private state - private BundleContext bc; +// private BundleContext bc; @SuppressWarnings("unchecked") @Override @@ -30,12 +29,12 @@ public class AnonymousLoginModule implements LoginModule { Map options) { this.subject = subject; this.sharedState = (Map) sharedState; - try { - bc = FrameworkUtil.getBundle(AnonymousLoginModule.class).getBundleContext(); - assert bc != null; - } catch (Exception e) { - throw new IllegalStateException("Cannot initialize login module", e); - } +// try { +// bc = FrameworkUtil.getBundle(AnonymousLoginModule.class).getBundleContext(); +// assert bc != null; +// } catch (Exception e) { +// throw new IllegalStateException("Cannot initialize login module", e); +// } } @Override @@ -45,7 +44,7 @@ public class AnonymousLoginModule implements LoginModule { @Override public boolean commit() throws LoginException { - UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class)); + UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin(); Authorization authorization = userAdmin.getAuthorization(null); RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST); Locale locale = Locale.getDefault(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java index 72676611e..289f8dcc6 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -1,30 +1,33 @@ package org.argeo.cms.auth; +import static org.argeo.api.cms.CmsConstants.ROLE_ADMIN; +import static org.argeo.api.cms.CmsConstants.ROLE_ANONYMOUS; +import static org.argeo.api.cms.CmsConstants.ROLE_USER; +import static org.argeo.api.cms.CmsConstants.ROLE_USER_ADMIN; + import java.security.Principal; -import java.util.Collection; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.UUID; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; +//import javax.naming.InvalidNameException; +//import javax.naming.ldap.LdapName; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; +import org.argeo.api.cms.AnonymousPrincipal; +import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsSession; import org.argeo.api.cms.CmsSessionId; import org.argeo.api.cms.DataAdminPrincipal; -import org.argeo.api.cms.AnonymousPrincipal; -import org.argeo.api.cms.CmsConstants; import org.argeo.cms.internal.auth.CmsSessionImpl; import org.argeo.cms.internal.auth.ImpliedByPrincipal; -import org.argeo.cms.internal.http.WebCmsSessionImpl; -import org.argeo.cms.security.NodeSecurityUtils; -import org.argeo.osgi.useradmin.AuthenticatingUser; -import org.osgi.framework.BundleContext; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpContext; +import org.argeo.cms.internal.auth.RemoteCmsSessionImpl; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.osgi.useradmin.AuthenticatingUser; import org.osgi.service.useradmin.Authorization; /** Centralises security related registrations. */ @@ -32,8 +35,8 @@ class CmsAuthUtils { // Standard final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME; final static String SHARED_STATE_PWD = AuthenticatingUser.SHARED_STATE_PWD; - final static String HEADER_AUTHORIZATION = "Authorization"; - final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; +// final static String HEADER_AUTHORIZATION = "Authorization"; +// final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; // Argeo specific final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request"; @@ -43,6 +46,11 @@ class CmsAuthUtils { final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr"; final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port"; + final static String SINGLE_USER_LOCAL_ID = "single-user"; + + private final static List RESERVED_ROLES = Collections + .unmodifiableList(Arrays.asList(new String[] { ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_USER, ROLE_USER_ADMIN })); + static void addAuthorization(Subject subject, Authorization authorization) { assert subject != null; checkSubjectEmpty(subject); @@ -54,46 +62,46 @@ class CmsAuthUtils { boolean singleUser = authorization instanceof SingleUserAuthorization; Set principals = subject.getPrincipals(); - try { - String authName = authorization.getName(); - - // determine user's principal - final LdapName name; - final Principal userPrincipal; - if (authName == null) { - name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME; - userPrincipal = new AnonymousPrincipal(); - principals.add(userPrincipal); - } else { - name = new LdapName(authName); - NodeSecurityUtils.checkUserName(name); - userPrincipal = new X500Principal(name.toString()); - principals.add(userPrincipal); - - if (singleUser) { - principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal)); - principals.add(new DataAdminPrincipal()); - } +// try { + String authName = authorization.getName(); + + // determine user's principal +// final LdapName name; + final Principal userPrincipal; + if (authName == null) { +// name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME; + userPrincipal = new AnonymousPrincipal(); + principals.add(userPrincipal); + } else { +// name = new LdapName(authName); + checkUserName(authName); + userPrincipal = new X500Principal(authName.toString()); + principals.add(userPrincipal); + + if (singleUser) { + principals.add(new ImpliedByPrincipal(CmsConstants.ROLE_ADMIN, userPrincipal)); + principals.add(new DataAdminPrincipal()); } + } - // Add roles provided by authorization - for (String role : authorization.getRoles()) { - LdapName roleName = new LdapName(role); - if (roleName.equals(name)) { - // skip - } else if (roleName.equals(NodeSecurityUtils.ROLE_ANONYMOUS_NAME)) { - // skip - } else { - NodeSecurityUtils.checkImpliedPrincipalName(roleName); - principals.add(new ImpliedByPrincipal(roleName.toString(), userPrincipal)); - if (roleName.equals(NodeSecurityUtils.ROLE_ADMIN_NAME)) - principals.add(new DataAdminPrincipal()); - } + // Add roles provided by authorization + for (String role : authorization.getRoles()) { +// LdapName roleName = new LdapName(role); + if (role.equals(authName)) { + // skip + } else if (role.equals(CmsConstants.ROLE_ANONYMOUS)) { + // skip + } else { +// NodeSecurityUtils.checkImpliedPrincipalName(role); + principals.add(new ImpliedByPrincipal(role, userPrincipal)); + if (role.equals(CmsConstants.ROLE_ADMIN)) + principals.add(new DataAdminPrincipal()); } - - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Cannot commit", e); } + +// } catch (InvalidNameException e) { +// throw new IllegalArgumentException("Cannot commit", e); +// } } private static void checkSubjectEmpty(Subject subject) { @@ -128,30 +136,33 @@ class CmsAuthUtils { // TODO move it to a service in order to avoid static synchronization if (request != null) { RemoteAuthSession httpSession = request.getSession(); - assert httpSession != null; - String httpSessId = httpSession.getId(); + String httpSessId = httpSession != null ? httpSession.getId() : null; boolean anonymous = authorization.getName() == null; String remoteUser = !anonymous ? authorization.getName() : CmsConstants.ROLE_ANONYMOUS; - request.setAttribute(HttpContext.REMOTE_USER, remoteUser); - request.setAttribute(HttpContext.AUTHORIZATION, authorization); + request.setAttribute(RemoteAuthRequest.REMOTE_USER, remoteUser); + request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization); CmsSessionImpl cmsSession; - CmsSessionImpl currentLocalSession = CmsSessionImpl.getByLocalId(httpSessId); + CmsSessionImpl currentLocalSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessId); if (currentLocalSession != null) { - boolean currentLocalSessionAnonymous = currentLocalSession.getAuthorization().getName() == null; + boolean currentLocalSessionAnonymous = currentLocalSession.isAnonymous(); if (!anonymous) { if (currentLocalSessionAnonymous) { currentLocalSession.close(); // new CMS session - cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request); + UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID(); + cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request); + CmsContextImpl.getCmsContext().registerCmsSession(cmsSession); } else if (!authorization.getName().equals(currentLocalSession.getAuthorization().getName())) { throw new IllegalStateException("Inconsistent user " + authorization.getName() + " for existing CMS session " + currentLocalSession); } else { // keep current session cmsSession = currentLocalSession; - // keyring - subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys()); + // credentials + // TODO control it more?? + subject.getPrivateCredentials().addAll(cmsSession.getSubject().getPrivateCredentials()); + subject.getPublicCredentials().addAll(cmsSession.getSubject().getPublicCredentials()); } } else {// anonymous if (!currentLocalSessionAnonymous) { @@ -164,7 +175,9 @@ class CmsAuthUtils { } } else { // new CMS session - cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request); + UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID(); + cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request); + CmsContextImpl.getCmsContext().registerCmsSession(cmsSession); } if (cmsSession == null)// should be dead code (cf. SuppressWarning of the method) @@ -175,39 +188,45 @@ class CmsAuthUtils { subject.getPrivateCredentials().add(nodeSessionId); } else { UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid(); - // if (storedSessionId.equals(httpSessionId.getValue())) - throw new IllegalStateException( - "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")"); + if (!storedSessionId.equals(nodeSessionId.getUuid())) + throw new IllegalStateException( + "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")"); } + request.setAttribute(CmsSession.class.getName(), cmsSession); } else { - CmsSessionImpl cmsSession = new CmsSessionImpl(subject, authorization, locale, "desktop"); + CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(SINGLE_USER_LOCAL_ID); + if (cmsSession == null) { + UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID(); + cmsSession = new CmsSessionImpl(cmsSessionUuid, subject, authorization, locale, SINGLE_USER_LOCAL_ID); + CmsContextImpl.getCmsContext().registerCmsSession(cmsSession); + } CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid()); subject.getPrivateCredentials().add(nodeSessionId); } } - public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) { - Authorization authorization = null; - Collection> sr; - try { - sr = bc.getServiceReferences(CmsSession.class, - "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")"); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e); - } - CmsSessionImpl cmsSession; - if (sr.size() == 1) { - cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next()); -// locale = cmsSession.getLocale(); - authorization = cmsSession.getAuthorization(); - if (authorization.getName() == null) - return null;// anonymous is not sufficient - } else if (sr.size() == 0) - return null; - else - throw new IllegalStateException(sr.size() + ">1 web sessions detected for http session " + httpSessionId); - return cmsSession; - } +// public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) { +// Authorization authorization = null; +// Collection> sr; +// try { +// sr = bc.getServiceReferences(CmsSession.class, +// "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")"); +// } catch (InvalidSyntaxException e) { +// throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e); +// } +// CmsSessionImpl cmsSession; +// if (sr.size() == 1) { +// cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next()); +//// locale = cmsSession.getLocale(); +// authorization = cmsSession.getAuthorization(); +// if (authorization.getName() == null) +// return null;// anonymous is not sufficient +// } else if (sr.size() == 0) +// return null; +// else +// throw new IllegalStateException(sr.size() + ">1 web sessions detected for http session " + httpSessionId); +// return cmsSession; +// } public static T getSinglePrincipal(Subject subject, Class clss) { Set principals = subject.getPrincipals(clss); @@ -218,6 +237,11 @@ class CmsAuthUtils { return principals.iterator().next(); } + private static void checkUserName(String name) throws IllegalArgumentException { + if (RESERVED_ROLES.contains(name)) + throw new IllegalArgumentException(name + " is a reserved name"); + } + private CmsAuthUtils() { } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java new file mode 100644 index 000000000..8834f3587 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java @@ -0,0 +1,33 @@ +package org.argeo.cms.auth; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ArgeoNamespace; +import org.argeo.api.acr.ContentName; +import org.argeo.cms.SystemRole; + +/** Standard CMS system roles. */ +public enum CmsRole implements SystemRole { + userAdmin, // + groupAdmin, // + // + ; + + private final static String QUALIFIER = "cms."; + + private final ContentName name; + + CmsRole() { + name = new ContentName(ArgeoNamespace.ROLE_NAMESPACE_URI, QUALIFIER + name()); + } + + @Override + public QName qName() { + return name; + } + + @Override + public String toString() { + return name.toPrefixedString(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java new file mode 100644 index 000000000..874381eb7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java @@ -0,0 +1,73 @@ +package org.argeo.cms.auth; + +import java.io.Console; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Scanner; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +/** Callback handler to be used with a command line UI. */ +public class ConsoleCallbackHandler implements CallbackHandler { + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + Console console = System.console(); +// if (console == null) +// throw new IllegalStateException("No console available"); + + Scanner scanner = null; + PrintWriter writer; + if (console == null) {// IDE + scanner = new Scanner(System.in); + writer = new PrintWriter(System.out, true); + } else { + writer = console.writer(); + + } + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof TextOutputCallback) { + TextOutputCallback callback = (TextOutputCallback) callbacks[i]; + writer.printf(callback.getMessage()); + } else if (callbacks[i] instanceof NameCallback) { + NameCallback callback = (NameCallback) callbacks[i]; + writer.printf(callback.getPrompt()); + if (callback.getDefaultName() != null) + writer.printf(" (" + callback.getDefaultName() + ")"); + String answer = console != null ? console.readLine() : scanner.next(); + if (callback.getDefaultName() != null && answer.trim().equals("")) + callback.setName(callback.getDefaultName()); + else + callback.setName(answer); + } else if (callbacks[i] instanceof PasswordCallback) { + PasswordCallback callback = (PasswordCallback) callbacks[i]; + writer.printf(callback.getPrompt()); + char[] answer = console != null ? console.readPassword() : scanner.next().toCharArray(); + callback.setPassword(answer); + Arrays.fill(answer, ' '); + } +// else if (callbacks[i] instanceof LocaleChoice) { +// LocaleChoice callback = (LocaleChoice) callbacks[i]; +// writer.write("Language"); +// writer.write("\n"); +// for (int j = 0; j < callback.getLocales().size(); j++) { +// Locale locale = callback.getLocales().get(j); +// writer.print(j + " : " + locale.getDisplayName() + "\n"); +// } +// writer.write("(" + callback.getDefaultIndex() + ") : "); +// String answer = console.readLine(); +// if (answer.trim().equals("")) +// callback.setSelectedIndex(callback.getDefaultIndex()); +// else +// callback.setSelectedIndex(new Integer(answer.trim())); +// } + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java deleted file mode 100644 index 85a482464..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.argeo.cms.auth; - -import java.security.AccessController; -import java.security.Principal; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; -import java.util.UUID; - -import javax.security.auth.Subject; -import javax.security.auth.x500.X500Principal; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.CmsSessionId; -import org.argeo.cms.internal.auth.CmsSessionImpl; -import org.argeo.cms.internal.auth.ImpliedByPrincipal; -import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.osgi.service.useradmin.Authorization; - -/** - * Programmatic access to the currently authenticated user, within a CMS - * context. - */ -public final class CurrentUser { - /* - * CURRENT USER API - */ - - /** - * Technical username of the currently authenticated user. - * - * @return the authenticated username or null if not authenticated / anonymous - */ - public static String getUsername() { - return getUsername(currentSubject()); - } - - /** - * Human readable name of the currently authenticated user (typically first name - * and last name). - */ - public static String getDisplayName() { - return getDisplayName(currentSubject()); - } - - /** Whether a user is currently authenticated. */ - public static boolean isAnonymous() { - return isAnonymous(currentSubject()); - } - - /** Locale of the current user */ - public final static Locale locale() { - return locale(currentSubject()); - } - - /** Roles of the currently logged-in user */ - public final static Set roles() { - return roles(currentSubject()); - } - - /** Returns true if the current user is in the specified role */ - public static boolean isInRole(String role) { - Set roles = roles(); - return roles.contains(role); - } - - /** Executes as the current user */ - public final static T doAs(PrivilegedAction action) { - return Subject.doAs(currentSubject(), action); - } - - /** Executes as the current user */ - public final static T tryAs(PrivilegedExceptionAction action) throws PrivilegedActionException { - return Subject.doAs(currentSubject(), action); - } - - /* - * WRAPPERS - */ - - public final static String getUsername(Subject subject) { - if (subject == null) - throw new IllegalArgumentException("Subject cannot be null"); - if (subject.getPrincipals(X500Principal.class).size() != 1) - return CmsConstants.ROLE_ANONYMOUS; - Principal principal = subject.getPrincipals(X500Principal.class).iterator().next(); - return principal.getName(); - } - - public final static String getDisplayName(Subject subject) { - return getAuthorization(subject).toString(); - } - - public final static Set roles(Subject subject) { - Set roles = new HashSet(); - roles.add(getUsername(subject)); - for (Principal group : subject.getPrincipals(ImpliedByPrincipal.class)) { - roles.add(group.getName()); - } - return roles; - } - - public final static Locale locale(Subject subject) { - Set locales = subject.getPublicCredentials(Locale.class); - if (locales.isEmpty()) { - Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale(); - return defaultLocale; - } else - return locales.iterator().next(); - } - - /** Whether this user is currently authenticated. */ - public static boolean isAnonymous(Subject subject) { - if (subject == null) - return true; - String username = getUsername(subject); - return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS); - } - - public CmsSession getCmsSession() { - Subject subject = currentSubject(); - CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next(); - return CmsSessionImpl.getByUuid(cmsSessionId.getUuid()); - } - - /* - * HELPERS - */ - private static Subject currentSubject() { - Subject subject = getAccessControllerSubject(); - if (subject != null) - return subject; - throw new IllegalStateException("Cannot find related subject"); - } - - private static Subject getAccessControllerSubject() { - return Subject.getSubject(AccessController.getContext()); - } - - private static Authorization getAuthorization(Subject subject) { - return subject.getPrivateCredentials(Authorization.class).iterator().next(); - } - - public static boolean logoutCmsSession(Subject subject) { - UUID nodeSessionId; - if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1) - nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid(); - else - return false; - CmsSessionImpl cmsSession = CmsSessionImpl.getByUuid(nodeSessionId.toString()); - - // FIXME logout all views - // TODO check why it is sometimes null - if (cmsSession != null) - cmsSession.close(); - // if (log.isDebugEnabled()) - // log.debug("Logged out CMS session " + cmsSession.getUuid()); - return true; - } - - private CurrentUser() { - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java index ea1046be9..d4f402853 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java @@ -45,4 +45,5 @@ public class DataAdminLoginModule implements LoginModule { subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class)); return true; } + } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java index c49a59ef1..ebab12f2c 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java @@ -1,6 +1,5 @@ package org.argeo.cms.auth; -import java.security.AccessController; import java.util.Map; import java.util.Set; @@ -15,8 +14,9 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -import org.argeo.cms.security.PBEKeySpecCallback; -import org.argeo.util.PasswordEncryption; +import org.argeo.api.cms.keyring.PBEKeySpecCallback; +import org.argeo.cms.util.CurrentSubject; +import org.argeo.cms.util.PasswordEncryption; /** Adds a secret key to the private credentials */ public class KeyringLoginModule implements LoginModule { @@ -28,7 +28,7 @@ public class KeyringLoginModule implements LoginModule { Map options) { this.subject = subject; if (subject == null) { - subject = Subject.getSubject(AccessController.getContext()); + this.subject = CurrentSubject.current(); } this.callbackHandler = callbackHandler; } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java index 2d1d14b4e..be5d0e15e 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java @@ -4,6 +4,9 @@ import java.util.Locale; /** Transitional interface to decouple from the Servlet API. */ public interface RemoteAuthRequest { + final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user"; + final static String AUTHORIZATION = "org.osgi.service.useradmin.authorization"; + RemoteAuthSession getSession(); RemoteAuthSession createSession(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java index f91b6c5de..b815b49d1 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java @@ -2,6 +2,9 @@ package org.argeo.cms.auth; /** Transitional interface to decouple from the Servlet API. */ public interface RemoteAuthResponse { - void setHeader(String keys, String value); + /** Set this header to a single value, possibly removing previous values. */ + void setHeader(String headerName, String value); + /** Add a value to this header. */ + void addHeader(String headerName, String value); } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java index d51997d74..3c436ba1f 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -1,64 +1,189 @@ package org.argeo.cms.auth; -import java.security.AccessControlContext; -import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Base64; import java.util.function.Supplier; import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosTicket; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsSession; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; +import org.argeo.cms.http.HttpHeader; +import org.argeo.cms.http.HttpStatus; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.util.CurrentSubject; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; /** Remote authentication utilities. */ public class RemoteAuthUtils { + private final static CmsLog log = CmsLog.getLog(RemoteAuthUtils.class); + static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user"; - private static BundleContext bundleContext = FrameworkUtil.getBundle(RemoteAuthUtils.class).getBundleContext(); + private final static Oid KERBEROS_OID; +// private final static Oid KERB_V5_OID, KRB5_PRINCIPAL_NAME_OID; + static { + try { + KERBEROS_OID = new Oid("1.3.6.1.5.5.2"); +// KERB_V5_OID = new Oid("1.2.840.113554.1.2.2"); +// KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1"); + } catch (GSSException e) { + throw new IllegalStateException("Cannot create Kerberos OID", e); + } + } /** * Execute this supplier, using the CMS class loader as context classloader. * Useful to log in to JCR. */ public final static T doAs(Supplier 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() { + CmsSession cmsSession = getCmsSession(req); + return CurrentSubject.callAs(cmsSession.getSubject(), () -> supplier.get()); +// ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); +// Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader()); +// try { +// return Subject.doAs( +// Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())), +// new PrivilegedAction() { +// +// @Override +// public T run() { +// return supplier.get(); +// } +// +// }); +// } finally { +// Thread.currentThread().setContextClassLoader(currentContextCl); +// } + } + +// public final static void configureRequestSecurity(RemoteAuthRequest req) { +// if (req.getAttribute(AccessControlContext.class.getName()) != null) +// throw new IllegalStateException("Request already authenticated."); +// AccessControlContext acc = AccessController.getContext(); +// req.setAttribute(REMOTE_USER, CurrentUser.getUsername()); +// req.setAttribute(AccessControlContext.class.getName(), acc); +// } +// +// public final static void clearRequestSecurity(RemoteAuthRequest req) { +// if (req.getAttribute(AccessControlContext.class.getName()) == null) +// throw new IllegalStateException("Cannot clear non-authenticated request."); +// req.setAttribute(REMOTE_USER, null); +// req.setAttribute(AccessControlContext.class.getName(), null); +// } + + public static CmsSession getCmsSession(RemoteAuthRequest req) { + CmsSession cmsSession = (CmsSession) req.getAttribute(CmsSession.class.getName()); + if (cmsSession == null) + throw new IllegalStateException("Request must have a CMS session attribute"); + return cmsSession; + } + + public static String createGssToken(Subject subject, String service, String server) { + if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty()) + throw new IllegalArgumentException("Subject " + subject + " is not GSS authenticated."); + return Subject.doAs(subject, (PrivilegedAction) () -> { + // !! different format than Kerberos + String serverPrinc = service + "@" + server; + GSSContext context = null; + String tokenStr = null; + + try { + // Get service's principal name + GSSManager manager = GSSManager.getInstance(); + // GSSName serverName = manager.createName(serverPrinc, + // GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID); + GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE); + + // Get the context for authentication + context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME); + // context.requestMutualAuth(true); // Request mutual authentication + // context.requestConf(true); // Request confidentiality + context.requestCredDeleg(true); - @Override - public T run() { - return supplier.get(); - } + byte[] token = new byte[0]; - }); + // token is ignored on the first call + token = context.initSecContext(token, 0, token.length); + + // Send a token to the server if one was generated by + // initSecContext + if (token != null) { + tokenStr = Base64.getEncoder().encodeToString(token); + // complete=true; + } + return tokenStr; + + } catch (GSSException e) { + throw new IllegalStateException("Cannot authenticate to " + serverPrinc, e); + } + }); + } + + public static LoginContext anonymousLogin(RemoteAuthRequest remoteAuthRequest, + RemoteAuthResponse remoteAuthResponse) { + // anonymous + ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader()); + LoginContext lc = CmsAuth.ANONYMOUS + .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse)); + lc.login(); + return lc; + } catch (LoginException e1) { + if (log.isDebugEnabled()) + log.error("Cannot log in as anonymous", e1); + return null; } finally { - Thread.currentThread().setContextClassLoader(currentContextCl); + Thread.currentThread().setContextClassLoader(currentContextClassLoader); } } - public final static void configureRequestSecurity(RemoteAuthRequest req) { - if (req.getAttribute(AccessControlContext.class.getName()) != null) - throw new IllegalStateException("Request already authenticated."); - AccessControlContext acc = AccessController.getContext(); - req.setAttribute(REMOTE_USER, CurrentUser.getUsername()); - req.setAttribute(AccessControlContext.class.getName(), acc); - } + public static int askForWwwAuth(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse, + String realm, boolean forceBasic) { + boolean negotiateFailed = false; + if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()) != null) { + // we already tried, so we give up in order not too loop endlessly + if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()) + .startsWith(HttpHeader.NEGOTIATE)) { + negotiateFailed = true; + } else { + return HttpStatus.FORBIDDEN.getCode(); + } + } - public final static void clearRequestSecurity(RemoteAuthRequest req) { - if (req.getAttribute(AccessControlContext.class.getName()) == null) - throw new IllegalStateException("Cannot clear non-authenticated request."); - req.setAttribute(REMOTE_USER, null); - req.setAttribute(AccessControlContext.class.getName(), null); + // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic + // realm=\"" + httpAuthRealm + "\""); + if (hasAcceptorCredentials() && !forceBasic && !negotiateFailed) {// SPNEGO + remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), HttpHeader.NEGOTIATE); + // TODO make it configurable ? + remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), + HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\""); + } else { + remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), + HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\""); + } + + // response.setDateHeader("Date", System.currentTimeMillis()); + // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * + // 60 * 60 * 1000)); + // response.setHeader("Accept-Ranges", "bytes"); + // response.setHeader("Connection", "Keep-Alive"); + // response.setHeader("Keep-Alive", "timeout=5, max=97"); + // response.setContentType("text/html; charset=UTF-8"); + + return HttpStatus.UNAUTHORIZED.getCode(); } - public static CmsSession getCmsSession(RemoteAuthRequest req) { - Subject subject = Subject - .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())); - CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject); - return cmsSession; + private static boolean hasAcceptorCredentials() { + return CmsContextImpl.getCmsContext().getAcceptorCredentials() != null; } + } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java index 962094d4a..987c3dd19 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -14,16 +14,15 @@ import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.http.HttpHeader; import org.argeo.cms.internal.auth.CmsSessionImpl; -import org.argeo.cms.internal.runtime.KernelUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.http.HttpContext; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.internal.runtime.CmsStateImpl; import org.osgi.service.useradmin.Authorization; -/** Use the HTTP session as the basis for authentication. */ +/** Use a remote session as the basis for authentication. */ public class RemoteSessionLoginModule implements LoginModule { private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class); @@ -34,8 +33,6 @@ public class RemoteSessionLoginModule implements LoginModule { private RemoteAuthRequest request = null; private RemoteAuthResponse response = null; - private BundleContext bc; - private Authorization authorization; private Locale locale; @@ -43,8 +40,6 @@ public class RemoteSessionLoginModule implements LoginModule { @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - bc = FrameworkUtil.getBundle(RemoteSessionLoginModule.class).getBundleContext(); - assert bc != null; this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = (Map) sharedState; @@ -54,49 +49,43 @@ public class RemoteSessionLoginModule implements LoginModule { public boolean login() throws LoginException { if (callbackHandler == null) return false; - RemoteAuthCallback httpCallback = new RemoteAuthCallback(); + RemoteAuthCallback remoteAuthCallback = new RemoteAuthCallback(); try { - callbackHandler.handle(new Callback[] { httpCallback }); + callbackHandler.handle(new Callback[] { remoteAuthCallback }); } catch (IOException e) { throw new LoginException("Cannot handle http callback: " + e.getMessage()); } catch (UnsupportedCallbackException e) { return false; } - request = httpCallback.getRequest(); + request = remoteAuthCallback.getRequest(); if (request == null) { - RemoteAuthSession httpSession = httpCallback.getHttpSession(); + RemoteAuthSession httpSession = remoteAuthCallback.getHttpSession(); if (httpSession == null) return false; // TODO factorize with below String httpSessionId = httpSession.getId(); -// if (log.isTraceEnabled()) -// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); - CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId); - if (cmsSession != null) { + CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId); + if (cmsSession != null && !cmsSession.isAnonymous()) { authorization = cmsSession.getAuthorization(); locale = cmsSession.getLocale(); if (log.isTraceEnabled()) log.trace("Retrieved authorization from " + cmsSession); } } else { - authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION); + authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION); if (authorization == null) {// search by session ID RemoteAuthSession httpSession = request.getSession(); - if (httpSession == null) { - // TODO make sure this is always safe - if (log.isTraceEnabled()) - log.trace("Create http session"); - httpSession = request.createSession(); - } - String httpSessionId = httpSession.getId(); -// if (log.isTraceEnabled()) -// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); - CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId); - if (cmsSession != null) { - authorization = cmsSession.getAuthorization(); - locale = cmsSession.getLocale(); - if (log.isTraceEnabled()) - log.trace("Retrieved authorization from " + cmsSession); + if (httpSession != null) { + String httpSessionId = httpSession.getId(); + CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId); + if (cmsSession != null && !cmsSession.isAnonymous()) { + authorization = cmsSession.getAuthorization(); + locale = cmsSession.getLocale(); + if (log.isTraceEnabled()) + log.trace("Retrieved authorization from " + cmsSession); + } + }else { + request.createSession(); } } sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request); @@ -110,7 +99,7 @@ public class RemoteSessionLoginModule implements LoginModule { } else { if (log.isTraceEnabled()) log.trace("HTTP login: " + true); - request.setAttribute(HttpContext.AUTHORIZATION, authorization); + request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization); return true; } } @@ -119,7 +108,7 @@ public class RemoteSessionLoginModule implements LoginModule { public boolean commit() throws LoginException { byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN); if (outToken != null) { - response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE, + response.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken)); } @@ -157,7 +146,7 @@ public class RemoteSessionLoginModule implements LoginModule { } private void extractHttpAuth(final RemoteAuthRequest httpRequest) { - String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION); + String authHeader = httpRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()); extractHttpAuth(authHeader); } @@ -166,7 +155,7 @@ public class RemoteSessionLoginModule implements LoginModule { StringTokenizer st = new StringTokenizer(authHeader); if (st.hasMoreTokens()) { String basic = st.nextToken(); - if (basic.equalsIgnoreCase("Basic")) { + if (basic.equalsIgnoreCase(HttpHeader.BASIC)) { try { // TODO manipulate char[] Base64.Decoder decoder = Base64.getDecoder(); @@ -184,7 +173,7 @@ public class RemoteSessionLoginModule implements LoginModule { } catch (Exception e) { throw new IllegalStateException("Couldn't retrieve authentication", e); } - } else if (basic.equalsIgnoreCase("Negotiate")) { + } else if (basic.equalsIgnoreCase(HttpHeader.NEGOTIATE)) { String spnegoToken = st.nextToken(); Base64.Decoder decoder = Base64.getDecoder(); byte[] authToken = decoder.decode(spnegoToken); @@ -192,15 +181,6 @@ public class RemoteSessionLoginModule implements LoginModule { } } } - - // auth token - // String mail = request.getParameter(LdapAttrs.mail.name()); - // String authPassword = request.getParameter(LdapAttrs.authPassword.name()); - // if (authPassword != null) { - // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword); - // if (mail != null) - // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail); - // } } private void extractClientCertificate(RemoteAuthRequest req) { @@ -212,7 +192,8 @@ public class RemoteSessionLoginModule implements LoginModule { if (log.isDebugEnabled()) log.debug("Client certificate " + certDn + " verified by servlet container"); } // Reverse proxy verified the client certificate - String clientDnHttpHeader = KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN); + String clientDnHttpHeader = CmsStateImpl.getDeployProperty(CmsContextImpl.getCmsContext().getCmsState(), + CmsDeployProperty.HTTP_PROXY_SSL_HEADER_DN); if (clientDnHttpHeader != null) { String certDn = req.getHeader(clientDnHttpHeader); // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java index 08380ac5a..4b36f28ab 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java @@ -1,7 +1,5 @@ package org.argeo.cms.auth; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.Locale; import java.util.Map; @@ -13,15 +11,15 @@ import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import javax.security.auth.x500.X500Principal; -import org.argeo.api.cms.CmsLog; -import org.argeo.osgi.useradmin.IpaUtils; -import org.argeo.osgi.useradmin.OsUserUtils; -import org.argeo.util.naming.LdapAttrs; +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.cms.directory.ldap.IpaUtils; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.osgi.useradmin.OsUserUtils; import org.osgi.service.useradmin.Authorization; /** Login module for when the system is owned by a single user. */ public class SingleUserLoginModule implements LoginModule { - private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class); +// private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class); private Subject subject; private Map sharedState = null; @@ -54,15 +52,9 @@ public class SingleUserLoginModule implements LoginModule { Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); if (username == null) throw new LoginException("No username available"); - String hostname; - try { - hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - log.warn("Using localhost as hostname", e); - hostname = "localhost"; - } + String hostname = CmsContextImpl.getCmsContext().getCmsState().getHostname(); String baseDn = ("." + hostname).replaceAll("\\.", ",dc="); - X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn); + X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + baseDn); authorizationName = principal.getName(); } @@ -74,8 +66,8 @@ public class SingleUserLoginModule implements LoginModule { locale = Locale.getDefault(); Authorization authorization = new SingleUserAuthorization(authorizationName); CmsAuthUtils.addAuthorization(subject, authorization); - - // Add standard Java OS login + + // Add standard Java OS login OsUserUtils.loginAsSystemUser(subject); // additional principals (must be after Authorization registration) diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java index 2dbad96d2..e5f367d23 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -1,6 +1,5 @@ package org.argeo.cms.auth; -import java.lang.reflect.Method; import java.util.Map; import javax.security.auth.Subject; @@ -11,11 +10,12 @@ import javax.security.auth.spi.LoginModule; import org.argeo.api.cms.CmsLog; import org.argeo.cms.internal.runtime.CmsContextImpl; import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; +import com.sun.security.jgss.GSSUtil; + /** SPNEGO login */ public class SpnegoLoginModule implements LoginModule { private final static CmsLog log = CmsLog.getLog(SpnegoLoginModule.class); @@ -36,25 +36,33 @@ public class SpnegoLoginModule implements LoginModule { @Override public boolean login() throws LoginException { byte[] spnegoToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN); - if (spnegoToken == null) + if (spnegoToken == null) { + if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) { + // workaround: set shared state name to empty + // in order to avoid Krb5LoginModule printing to System.out + // TODO ask upstream to only log in debug mode + sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, ""); + } return false; + } gssContext = checkToken(spnegoToken); if (gssContext == null) return false; - else + else { + if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) { + try { + if (gssContext.getCredDelegState()) { + // commit will succeeed only if we have credential delegation + GSSName name = gssContext.getSrcName(); + String username = name.toString(); + sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username); + } + } catch (GSSException e) { + throw new IllegalStateException("Cannot retrieve SPNEGO name", e); + } + } return true; - // try { - // String clientName = gssContext.getSrcName().toString(); - // String role = clientName.substring(clientName.indexOf('@') + 1); - // - // log.debug("SpnegoUserRealm: established a security context"); - // log.debug("Client Principal is: " + gssContext.getSrcName()); - // log.debug("Server Principal is: " + gssContext.getTargName()); - // log.debug("Client Default Role: " + role); - // } catch (GSSException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } + } } @Override @@ -63,14 +71,12 @@ public class SpnegoLoginModule implements LoginModule { return false; try { - Class gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil"); - Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class); Subject gssSubject; if (gssContext.getCredDelegState()) - gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(), - gssContext.getDelegCred()); + gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), gssContext.getDelegCred()); else - gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(), null); + gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), null); + // without credential delegation we won't have access to the Kerberos key subject.getPrincipals().addAll(gssSubject.getPrincipals()); subject.getPrivateCredentials().addAll(gssSubject.getPrivateCredentials()); return true; @@ -111,8 +117,7 @@ public class SpnegoLoginModule implements LoginModule { private GSSContext checkToken(byte[] authToken) { GSSManager manager = GSSManager.getInstance(); try { - GSSContext gContext = manager.createContext(CmsContextImpl.getAcceptorCredentials()); - + GSSContext gContext = manager.createContext(CmsContextImpl.getCmsContext().getAcceptorCredentials()); if (gContext == null) { log.debug("SpnegoUserRealm: failed to establish GSSContext"); } else { @@ -132,8 +137,4 @@ public class SpnegoLoginModule implements LoginModule { } - @Deprecated - public static boolean hasAcceptorCredentials() { - return CmsContextImpl.getAcceptorCredentials() != null; - } } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 738b507e7..2b5c41ddf 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -1,8 +1,9 @@ package org.argeo.cms.auth; -import static org.argeo.util.naming.LdapAttrs.cn; +import static org.argeo.api.acr.ldap.LdapAttr.cn; import java.io.IOException; +import java.security.Principal; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.HashSet; @@ -24,18 +25,13 @@ import javax.security.auth.login.CredentialNotFoundException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; +import org.argeo.api.acr.ldap.LdapAttr; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.osgi.NodeUserAdmin; +import org.argeo.cms.directory.ldap.IpaUtils; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.osgi.useradmin.AuthenticatingUser; -import org.argeo.osgi.useradmin.IpaUtils; -import org.argeo.osgi.useradmin.TokenUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; +import org.argeo.cms.osgi.useradmin.AuthenticatingUser; +import org.argeo.cms.osgi.useradmin.TokenUtils; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.User; @@ -52,11 +48,11 @@ public class UserAdminLoginModule implements LoginModule { private CallbackHandler callbackHandler; private Map sharedState = null; - private List indexedUserProperties = Arrays - .asList(new String[] { LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() }); + private List indexedUserProperties = Arrays.asList(new String[] { LdapAttr.mail.name(), LdapAttr.uid.name(), + LdapAttr.employeeNumber.name(), LdapAttr.authPassword.name() }); // private state - private BundleContext bc; +// private BundleContext bc; private User authenticatedUser = null; private Locale locale; @@ -70,7 +66,7 @@ public class UserAdminLoginModule implements LoginModule { Map options) { this.subject = subject; try { - bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext(); +// bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext(); this.callbackHandler = callbackHandler; this.sharedState = (Map) sharedState; } catch (Exception e) { @@ -80,7 +76,7 @@ public class UserAdminLoginModule implements LoginModule { @Override public boolean login() throws LoginException { - UserAdmin userAdmin = CmsContextImpl.getUserAdmin(); + UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin(); final String username; final char[] password; Object certificateChain = null; @@ -91,17 +87,13 @@ public class UserAdminLoginModule implements LoginModule { username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD); // // TODO locale? + } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME) + && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN)) { + // SPNEGO login has succeeded, that's enough for us at this stage + return true; } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME) && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN)) { String certDn = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); -// LdapName ldapName; -// try { -// ldapName = new LdapName(certificateName); -// } catch (InvalidNameException e) { -// e.printStackTrace(); -// return false; -// } -// username = ldapName.getRdn(ldapName.size() - 1).getValue().toString(); username = certDn; certificateChain = sharedState.get(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN); password = null; @@ -111,11 +103,6 @@ public class UserAdminLoginModule implements LoginModule { username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); password = null; preauth = true; -// } else if (singleUser) { -// username = OsUserUtils.getOsUsername(); -// password = null; -// // TODO retrieve from http session -// locale = Locale.getDefault(); } else { // ask for username and password @@ -169,20 +156,24 @@ public class UserAdminLoginModule implements LoginModule { return true;// expect Kerberos if (password != null) { + // TODO disabling bind for the time being, + // as it requires authorisations to be set at LDAP level + boolean tryBind = false; // try bind first - try { - AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password); - bindAuthorization = userAdmin.getAuthorization(authenticatingUser); - // TODO check tokens as well - if (bindAuthorization != null) { - authenticatedUser = user; - return true; + if (tryBind) + try { + AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password); + bindAuthorization = userAdmin.getAuthorization(authenticatingUser); + // TODO check tokens as well + if (bindAuthorization != null) { + authenticatedUser = user; + return true; + } + } catch (Exception e) { + // silent + if (log.isTraceEnabled()) + log.trace("Bind failed", e); } - } catch (Exception e) { - // silent - if (log.isTraceEnabled()) - log.trace("Bind failed", e); - } // works only if a connection password is provided if (!user.hasCredential(null, password)) { @@ -212,7 +203,7 @@ public class UserAdminLoginModule implements LoginModule { // if (singleUser) { // OsUserUtils.loginAsSystemUser(subject); // } - UserAdmin userAdmin = CmsContextImpl.getUserAdmin(); + UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin(); Authorization authorization; if (callbackHandler == null) {// anonymous authorization = userAdmin.getAuthorization(null); @@ -237,6 +228,8 @@ public class UserAdminLoginModule implements LoginModule { throw new LoginException("Kerberos login " + authenticatingUser.getName() + " is inconsistent with user admin login " + authenticatedUser.getName()); } + if (log.isTraceEnabled()) + log.trace("Retrieve authorization for " + authenticatingUser + "... "); authorization = Subject.doAs(subject, new PrivilegedAction() { @Override @@ -256,34 +249,47 @@ public class UserAdminLoginModule implements LoginModule { CmsAuthUtils.addAuthorization(subject, authorization); // Unlock keyring (underlying login to the JCR repository) - char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD); - if (password != null) { - ServiceReference keyringSr = bc.getServiceReference(CryptoKeyring.class); - if (keyringSr != null) { - CryptoKeyring keyring = bc.getService(keyringSr); - Subject.doAs(subject, new PrivilegedAction() { - - @Override - public Void run() { - try { - keyring.unlock(password); - } catch (Exception e) { - e.printStackTrace(); - log.warn("Could not unlock keyring with the password provided by " + authorization.getName() - + ": " + e.getMessage()); - } - return null; - } - - }); - } - } +// char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD); +// if (password != null) { +// ServiceReference keyringSr = bc.getServiceReference(CryptoKeyring.class); +// if (keyringSr != null) { +// CryptoKeyring keyring = bc.getService(keyringSr); +// Subject.doAs(subject, new PrivilegedAction() { +// +// @Override +// public Void run() { +// try { +// keyring.unlock(password); +// } catch (Exception e) { +// e.printStackTrace(); +// log.warn("Could not unlock keyring with the password provided by " + authorization.getName() +// + ": " + e.getMessage()); +// } +// return null; +// } +// +// }); +// } +// } // Register CmsSession with initial subject CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale); - if (log.isDebugEnabled()) - log.debug("Logged in to CMS: " + subject); + if (log.isDebugEnabled()) { + StringBuilder msg = new StringBuilder(); + msg.append("Logged in to CMS: " + authorization.getName() + "(" + authorization + ")\n"); + for (Principal principal : subject.getPrincipals()) { + msg.append(" Principal: " + principal.getName()).append(" (") + .append(principal.getClass().getSimpleName()).append(")\n"); + } + for (Object credential : subject.getPublicCredentials()) { + msg.append(" Public Credential: " + credential).append(" (") + .append(credential.getClass().getSimpleName()).append(")\n"); + } + log.debug(msg); + } +// if (log.isTraceEnabled()) +// log.trace(" Subject: " + subject); return true; } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java index eed38cc32..bef6d7f0a 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java @@ -6,8 +6,9 @@ import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; +import org.argeo.api.acr.ldap.LdapAttr; import org.argeo.api.cms.CmsConstants; -import org.argeo.util.naming.LdapAttrs; +import org.argeo.cms.CurrentUser; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; @@ -18,7 +19,7 @@ public class UserAdminUtils { // CURRENTUSER HELPERS /** Checks if current user is the same as the passed one */ public static boolean isCurrentUser(User user) { - String userUsername = getProperty(user, LdapAttrs.DN); + String userUsername = getProperty(user, LdapAttr.DN); LdapName userLdapName = getLdapName(userUsername); LdapName selfUserName = getCurrentUserLdapName(); return userLdapName.equals(selfUserName); @@ -43,7 +44,7 @@ public class UserAdminUtils { /** Retrieves the current logged-in user common name */ public final static String getCommonName(User user) { - return getProperty(user, LdapAttrs.cn.name()); + return getProperty(user, LdapAttr.cn.name()); } // OTHER USERS HELPERS @@ -54,8 +55,8 @@ public class UserAdminUtils { public static String getUserLocalId(String dn) { LdapName ldapName = getLdapName(dn); Rdn last = ldapName.getRdn(ldapName.size() - 1); - if (last.getType().toLowerCase().equals(LdapAttrs.uid.name()) - || last.getType().toLowerCase().equals(LdapAttrs.cn.name())) + if (last.getType().toLowerCase().equals(LdapAttr.uid.name()) + || last.getType().toLowerCase().equals(LdapAttr.cn.name())) return (String) last.getValue(); else throw new IllegalArgumentException("Cannot retrieve user local id, non valid dn: " + dn); @@ -67,16 +68,19 @@ public class UserAdminUtils { */ public static String getUserDisplayName(UserAdmin userAdmin, String dn) { Role user = userAdmin.getRole(dn); - String dName; if (user == null) - dName = getUserLocalId(dn); - else { - dName = getProperty(user, LdapAttrs.displayName.name()); - if (isEmpty(dName)) - dName = getProperty(user, LdapAttrs.cn.name()); - if (isEmpty(dName)) - dName = getUserLocalId(dn); - } + return getUserLocalId(dn); + return getUserDisplayName(user); + } + + public static String getUserDisplayName(Role user) { + String dName = getProperty(user, LdapAttr.displayName.name()); + if (isEmpty(dName)) + dName = getProperty(user, LdapAttr.cn.name()); + if (isEmpty(dName)) + dName = getProperty(user, LdapAttr.uid.name()); + if (isEmpty(dName)) + dName = getUserLocalId(user.getName()); return dName; } @@ -89,7 +93,7 @@ public class UserAdminUtils { if (user == null) return null; else - return getProperty(user, LdapAttrs.mail.name()); + return getProperty(user, LdapAttr.mail.name()); } // LDAP NAMES HELPERS @@ -122,7 +126,7 @@ public class UserAdminUtils { } /** - * Simply retrieves a LDAP name from a {@link LdapAttrs.DN} with no exception + * Simply retrieves a LDAP name from a {@link LdapAttr.DN} with no exception */ private static LdapName getLdapName(String dn) { try { @@ -135,7 +139,7 @@ public class UserAdminUtils { /** Simply retrieves a display name of the relevant domain */ public final static String getDomainName(User user) { String dn = user.getName(); - if (dn.endsWith(CmsConstants.ROLES_BASEDN)) + if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN)) return "System roles"; if (dn.endsWith(CmsConstants.TOKENS_BASEDN)) return "Tokens"; @@ -147,8 +151,8 @@ public class UserAdminUtils { int i = 0; loop: while (i < rdns.size()) { Rdn currrRdn = rdns.get(i); - if (LdapAttrs.uid.name().equals(currrRdn.getType()) || LdapAttrs.cn.name().equals(currrRdn.getType()) - || LdapAttrs.ou.name().equals(currrRdn.getType())) + if (LdapAttr.uid.name().equals(currrRdn.getType()) || LdapAttr.cn.name().equals(currrRdn.getType()) + || LdapAttr.ou.name().equals(currrRdn.getType())) break loop; else { String currVal = (String) currrRdn.getValue(); diff --git a/org.argeo.cms/src/org/argeo/cms/client/CmsClient.java b/org.argeo.cms/src/org/argeo/cms/client/CmsClient.java new file mode 100644 index 000000000..8cfae3a1b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/client/CmsClient.java @@ -0,0 +1,172 @@ +package org.argeo.cms.client; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.BodyHandlers; +import java.net.http.WebSocket; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.cms.auth.ConsoleCallbackHandler; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.http.HttpHeader; + +/** Utility to connect to a remote CMS node. */ +public class CmsClient { + public final static String CLIENT_LOGIN_CONTEXT = "CLIENT"; + + private URI uri; + + private HttpClient httpClient; + private String gssToken; + + public CmsClient(URI uri) { + this.uri = uri; + } + + public void login() { + String server = uri.getHost(); + + URL jaasUrl = CmsClient.class.getResource("jaas-client-ipa.cfg"); + System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm()); + try { + LoginContext lc = new LoginContext(CLIENT_LOGIN_CONTEXT, new ConsoleCallbackHandler()); + lc.login(); + gssToken = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", server); + } catch (LoginException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + + } + } + + public String getAsString() { + return getAsString(uri); + } + + public String getAsString(URI uri) { + uri = normalizeUri(uri); + try { + HttpClient httpClient = getHttpClient(); + + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + getGssToken()) // + .build(); + BodyHandler bodyHandler = BodyHandlers.ofString(); + HttpResponse response = httpClient.send(request, bodyHandler); + return response.body(); +// int responseCode = response.statusCode(); +// System.exit(responseCode); + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Cannot read " + uri + " as a string", e); + } + } + + protected URI normalizeUri(URI uri) { + if (uri.getHost() != null) + return uri; + try { + String path = uri.getPath(); + if (path.startsWith("/")) {// absolute + return new URI(this.uri.getScheme(), this.uri.getUserInfo(), this.uri.getHost(), this.uri.getPort(), + path, uri.getQuery(), uri.getFragment()); + } else { + String thisUriStr = this.uri.toString(); + if (!thisUriStr.endsWith("/")) + thisUriStr = thisUriStr + "/"; + return URI.create(thisUriStr + path); + } + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot interpret " + uri, e); + } + } + + public URI getUri() { + return uri; + } + + String getGssToken() { + return gssToken; + } + + public HttpClient getHttpClient() { + if (httpClient == null) { + login(); + HttpClient client = HttpClient.newBuilder() // + .sslContext(ipaSslContext()) // + .version(HttpClient.Version.HTTP_1_1) // + .build(); + httpClient = client; + } + return httpClient; + } + + public CompletableFuture newWebSocket(WebSocket.Listener listener) { + return newWebSocket(uri, listener); + } + + public CompletableFuture newWebSocket(URI uri, WebSocket.Listener listener) { + CompletableFuture ws = getHttpClient().newWebSocketBuilder() + .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + getGssToken()) + .buildAsync(uri, listener); + return ws; + } + + @SuppressWarnings("unchecked") + protected SSLContext ipaSslContext() { + try { + final Collection certificates; + Path caCertificatePath = Paths.get("/etc/ipa/ca.crt"); + if (Files.exists(caCertificatePath)) { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); + try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(caCertificatePath))) { + certificates = (Collection) certificateFactory.generateCertificates(in); + } + } else { + certificates = null; + } + TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] xcs, String string) { + } + + public void checkServerTrusted(X509Certificate[] xcs, String string) { + } + + public X509Certificate[] getAcceptedIssuers() { + if (certificates == null) + return null; + return certificates.toArray(new X509Certificate[certificates.size()]); + } + } }; + + SSLContext sc = SSLContext.getInstance("ssl"); + sc.init(null, noopTrustManager, null); + return sc; + } catch (KeyManagementException | NoSuchAlgorithmException | CertificateException | IOException e) { + throw new IllegalStateException("Cannot create SSL context ", e); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java new file mode 100644 index 000000000..e8dd2fa52 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java @@ -0,0 +1,60 @@ +package org.argeo.cms.client; + +import java.net.URI; +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +/** Tests connectivity to the web socket server. */ +public class WebSocketEventClient implements Runnable { + + private final URI uri; + + private WebSocket webSocket; + + private CmsClient cmsClient; + + public WebSocketEventClient(URI uri) { + this.uri = uri; + cmsClient = new CmsClient(uri); + } + + @Override + public void run() { + try { + CompletableFuture ws = cmsClient.newWebSocket(new WsEventListener()); + + WebSocket webSocket = ws.get(); + webSocket.request(Long.MAX_VALUE); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""))); + + while (!webSocket.isInputClosed()) { + webSocket.sendPing(ByteBuffer.allocate(0)); + Thread.sleep(10000); + } + } catch (InterruptedException e) { + if (webSocket != null) + webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""); + } catch (ExecutionException e) { + throw new RuntimeException("Cannot listent to " + uri, e.getCause()); + } + } + + private class WsEventListener implements WebSocket.Listener { + public CompletionStage onText(WebSocket webSocket, CharSequence message, boolean last) { + System.out.println(message); + CompletionStage res = CompletableFuture.completedStage(message.toString()); + return res; + } + + @Override + public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { + // System.out.println("Pong received."); + return null; + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java new file mode 100644 index 000000000..808c8de68 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java @@ -0,0 +1,90 @@ +package org.argeo.cms.client; + +import java.math.RoundingMode; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.text.DecimalFormat; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; + +/** Tests connectivity to the web socket server. */ +public class WebSocketPing implements Runnable { + private final static int PING_FRAME_SIZE = 125; + private final static DecimalFormat decimalFormat = new DecimalFormat("0.0"); + static { + decimalFormat.setRoundingMode(RoundingMode.HALF_UP); + } + + private final URI uri; + private final UUID uuid; + + private WebSocket webSocket; + + public WebSocketPing(URI uri) { + this.uri = uri; + this.uuid = UUID.randomUUID(); + } + + @Override + public void run() { + try { + WebSocket.Listener listener = new WebSocket.Listener() { + + @Override + public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { + long msb = message.getLong(); + long lsb = message.getLong(); + long end = System.nanoTime(); + if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits()) + return null; // ignore + long begin = message.getLong(); + double durationNs = end - begin; + double durationMs = durationNs / 1000000; + int size = message.remaining() + (3 * Long.BYTES); + System.out.println( + size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms"); + return null; + } + + }; + + HttpClient client = HttpClient.newHttpClient(); + CompletableFuture ws = client.newWebSocketBuilder().buildAsync(uri, listener); + webSocket = ws.get(); + webSocket.request(Long.MAX_VALUE); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""))); + + while (!webSocket.isInputClosed()) { + long begin = System.nanoTime(); + ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + buffer.putLong(begin); + buffer.flip(); + webSocket.sendPing(buffer); + Thread.sleep(1000); + } + } catch (InterruptedException e) { + if (webSocket != null) + webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""); + } catch (ExecutionException e) { + throw new RuntimeException("Cannot ping " + uri, e.getCause()); + } + } + +// public static void main(String[] args) throws Exception { +// if (args.length == 0) { +// System.err.println("usage: java " + WsPing.class.getName() + " "); +// System.exit(1); +// return; +// } +// URI uri = URI.create(args[0]); +// new WsPing(uri).run(); +// } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg new file mode 100644 index 000000000..b776c2c5c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg @@ -0,0 +1,4 @@ +CLIENT { + com.sun.security.auth.module.Krb5LoginModule required + useTicketCache=true; +}; diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java b/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java new file mode 100644 index 000000000..6fe2eb617 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java @@ -0,0 +1,162 @@ +package org.argeo.cms.dav; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.Iterator; + +import javax.xml.namespace.QName; + +import org.argeo.cms.http.HttpHeader; +import org.argeo.cms.http.HttpMethod; +import org.argeo.cms.http.HttpStatus; + +public class DavClient { + + private HttpClient httpClient; + + public DavClient() { + httpClient = HttpClient.newBuilder() // +// .sslContext(insecureContext()) // + .version(HttpClient.Version.HTTP_1_1) // + .authenticator(new Authenticator() { + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("root", "demo".toCharArray()); + } + + }) // + .build(); + } + + public void setProperty(String url, QName key, String value) { + try { + String body = """ + + " + // + """ + + + """ // + + "<" + key.getPrefix() + ":" + key.getLocalPart() + ">" + value + "" + // + """ + + + + """; + System.out.println(body); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)) // + .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_1.getValue()) // + .method(HttpMethod.PROPPATCH.name(), BodyPublishers.ofString(body)) // + .build(); + BodyHandler bodyHandler = BodyHandlers.ofString(); + HttpResponse response = httpClient.send(request, bodyHandler); + System.out.println(response.body()); + } catch (IOException | InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public Iterator listChildren(URI uri) { + try { + String body = """ + + + + """; + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_1.getValue()) // + .method(HttpMethod.PROPFIND.name(), BodyPublishers.ofString(body)) // + .build(); + + HttpResponse responseStr = httpClient.send(request, BodyHandlers.ofString()); + System.out.println(responseStr.body()); + + HttpResponse response = httpClient.send(request, BodyHandlers.ofInputStream()); + MultiStatusReader msReader = new MultiStatusReader(response.body(), uri.getPath()); + return msReader; + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Cannot list children of " + uri, e); + } + + } + + public boolean exists(URI uri) { + try { + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_0.getValue()) // + .method(HttpMethod.HEAD.name(), BodyPublishers.noBody()) // + .build(); + BodyHandler bodyHandler = BodyHandlers.ofString(); + HttpResponse response = httpClient.send(request, bodyHandler); + System.out.println(response.body()); + int responseStatusCode = response.statusCode(); + if (responseStatusCode == HttpStatus.NOT_FOUND.getCode()) + return false; + if (responseStatusCode >= 200 && responseStatusCode < 300) + return true; + throw new IllegalStateException( + "Cannot check whether " + uri + " exists: Unknown response status code " + responseStatusCode); + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Cannot check whether " + uri + " exists", e); + } + + } + + public DavResponse get(URI uri) { + try { + String body = """ + + + + """; + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_0.getValue()) // + .method(HttpMethod.PROPFIND.name(), BodyPublishers.ofString(body)) // + .build(); + +// HttpResponse responseStr = httpClient.send(request, BodyHandlers.ofString()); +// System.out.println(responseStr.body()); + + HttpResponse response = httpClient.send(request, BodyHandlers.ofInputStream()); + MultiStatusReader msReader = new MultiStatusReader(response.body()); + if (!msReader.hasNext()) + throw new IllegalArgumentException(uri + " does not exist"); + return msReader.next(); + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Cannot list children of " + uri, e); + } + + } + + public static void main(String[] args) { + DavClient davClient = new DavClient(); +// Iterator responses = davClient +// .listChildren(URI.create("http://localhost/unstable/a2/org.argeo.tp.sdk/")); + Iterator responses = davClient + .listChildren(URI.create("http://root:demo@localhost:7070/api/acr/srv/example")); + while (responses.hasNext()) { + DavResponse response = responses.next(); + System.out.println(response.getHref() + (response.isCollection() ? " (collection)" : "")); + //System.out.println(" " + response.getPropertyNames(HttpStatus.OK)); + + } +// davClient.setProperty("http://localhost/unstable/a2/org.argeo.tp.sdk/org.opentest4j.1.2.jar", +// CrName.uuid.qName(), UUID.randomUUID().toString()); + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java b/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java new file mode 100644 index 000000000..c7542b55a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java @@ -0,0 +1,37 @@ +package org.argeo.cms.dav; + +import org.argeo.cms.http.HttpHeader; + +import com.sun.net.httpserver.HttpExchange; + +public enum DavDepth { + DEPTH_0("0"), DEPTH_1("1"), DEPTH_INFINITY("infinity"); + + private final String value; + + private DavDepth(String value) { + this.value = value; + } + + @Override + public String toString() { + return getValue(); + } + + public String getValue() { + return value; + } + + public static DavDepth fromHttpExchange(HttpExchange httpExchange) { + String value = httpExchange.getRequestHeaders().getFirst(HttpHeader.DEPTH.getHeaderName()); + if (value == null) + return null; + DavDepth depth = switch (value) { + case "0" -> DEPTH_0; + case "1" -> DEPTH_1; + case "infinity" -> DEPTH_INFINITY; + default -> throw new IllegalArgumentException("Unexpected value: " + value); + }; + return depth; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java new file mode 100644 index 000000000..63f4f82c7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java @@ -0,0 +1,98 @@ +package org.argeo.cms.dav; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import javax.xml.namespace.NamespaceContext; + +import org.argeo.api.acr.ContentNotFoundException; +import org.argeo.cms.http.HttpHeader; +import org.argeo.cms.http.HttpMethod; +import org.argeo.cms.http.HttpStatus; +import org.argeo.cms.http.server.HttpServerUtils; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +/** + * Centralise patterns which are not ACR specific. Not really meant as a + * framework for building WebDav servers, but rather to make upper-level of + * ACR-specific code more readable and maintainable. + */ +public abstract class DavHttpHandler implements HttpHandler { + + @Override + public void handle(HttpExchange exchange) throws IOException { + String subPath = HttpServerUtils.subPath(exchange); + String method = exchange.getRequestMethod(); + try { + if (HttpMethod.GET.name().equals(method)) { + handleGET(exchange, subPath); + } else if (HttpMethod.OPTIONS.name().equals(method)) { + handleOPTIONS(exchange, subPath); + exchange.sendResponseHeaders(HttpStatus.NO_CONTENT.getCode(), -1); + } else if (HttpMethod.PROPFIND.name().equals(method)) { + DavDepth depth = DavDepth.fromHttpExchange(exchange); + if (depth == null) { + // default, as per http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND + depth = DavDepth.DEPTH_INFINITY; + } + DavPropfind davPropfind; + try (InputStream in = exchange.getRequestBody()) { + davPropfind = DavPropfind.load(depth, in); + } + MultiStatusWriter multiStatusWriter = new MultiStatusWriter(exchange.getProtocol()); + CompletableFuture published = handlePROPFIND(exchange, subPath, davPropfind, multiStatusWriter); + exchange.sendResponseHeaders(HttpStatus.MULTI_STATUS.getCode(), 0l); + NamespaceContext namespaceContext = getNamespaceContext(exchange, subPath); + try (OutputStream out = exchange.getResponseBody()) { + multiStatusWriter.process(namespaceContext, out, published.minimalCompletionStage(), + davPropfind.isPropname()); + } + } else { + throw new IllegalArgumentException("Unsupported method " + method); + } + } catch (ContentNotFoundException e) { + exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1); + } + // TODO return a structured error message + catch (UnsupportedOperationException e) { + e.printStackTrace(); + exchange.sendResponseHeaders(HttpStatus.NOT_IMPLEMENTED.getCode(), -1); + } catch (Exception e) { + exchange.sendResponseHeaders(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), -1); + } + + } + + protected abstract NamespaceContext getNamespaceContext(HttpExchange httpExchange, String path); + + protected abstract CompletableFuture handlePROPFIND(HttpExchange exchange, String path, + DavPropfind davPropfind, Consumer consumer) throws IOException; + + protected abstract void handleGET(HttpExchange exchange, String path) throws IOException; + + protected void handleOPTIONS(HttpExchange exchange, String path) throws IOException { + exchange.getResponseHeaders().set(HttpHeader.DAV.getHeaderName(), "1, 3"); + StringJoiner methods = new StringJoiner(","); + methods.add(HttpMethod.OPTIONS.name()); + methods.add(HttpMethod.HEAD.name()); + methods.add(HttpMethod.GET.name()); + methods.add(HttpMethod.POST.name()); + methods.add(HttpMethod.PUT.name()); + methods.add(HttpMethod.PROPFIND.name()); + // TODO : + methods.add(HttpMethod.PROPPATCH.name()); + methods.add(HttpMethod.MKCOL.name()); + methods.add(HttpMethod.DELETE.name()); + methods.add(HttpMethod.MOVE.name()); + methods.add(HttpMethod.COPY.name()); + + exchange.getResponseHeaders().add(HttpHeader.ALLOW.getHeaderName(), methods.toString()); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavPropfind.java b/org.argeo.cms/src/org/argeo/cms/dav/DavPropfind.java new file mode 100644 index 000000000..8160544e9 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavPropfind.java @@ -0,0 +1,92 @@ +package org.argeo.cms.dav; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +public class DavPropfind { + private DavDepth depth; + private boolean propname = false; + private boolean allprop = false; + private List props = new ArrayList<>(); + + public DavPropfind(DavDepth depth) { + this.depth = depth; + } + + public boolean isPropname() { + return propname; + } + + public void setPropname(boolean propname) { + this.propname = propname; + } + + public boolean isAllprop() { + return allprop; + } + + public void setAllprop(boolean allprop) { + this.allprop = allprop; + } + + public List getProps() { + return props; + } + + public DavDepth getDepth() { + return depth; + } + + public static DavPropfind load(DavDepth depth, InputStream in) throws IOException { + try { + DavPropfind res = null; + XMLInputFactory inputFactory = XMLInputFactory.newFactory(); + XMLStreamReader reader = inputFactory.createXMLStreamReader(in); + while (reader.hasNext()) { + reader.next(); + if (reader.isStartElement()) { + QName name = reader.getName(); +// System.out.println(name); + DavXmlElement davXmlElement = DavXmlElement.toEnum(name); + if (davXmlElement != null) { + switch (davXmlElement) { + case propfind: + res = new DavPropfind(depth); + break; + case allprop: + res.setAllprop(true); + break; + case propname: + res.setPropname(true); + case prop: + // ignore + case include: + // ignore + break; + default: + // TODO check that the format is really respected + res.getProps().add(reader.getName()); + } + } + } + } + + // checks + if (res.isPropname()) { + if (!res.getProps().isEmpty() || res.isAllprop()) + throw new IllegalArgumentException("Cannot set other values if propname is set"); + } + return res; + } catch (FactoryConfigurationError | XMLStreamException e) { + throw new RuntimeException("Cannot load propfind", e); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java new file mode 100644 index 000000000..8dd6bf3fc --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java @@ -0,0 +1,59 @@ +package org.argeo.cms.dav; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.xml.namespace.QName; + +import org.argeo.cms.http.HttpStatus; + +/** The WebDav response for a given resource. */ +public class DavResponse { + final static String MOD_DAV_NAMESPACE = "http://apache.org/dav/props/"; + + private String href; + private boolean collection; + private Map> propertyNames = new TreeMap<>(); + private Map properties = new HashMap<>(); + private List resourceTypes = new ArrayList<>(); + + public Map getProperties() { + return properties; + } + + public void setHref(String href) { + this.href = href; + } + + public String getHref() { + return href; + } + + public boolean isCollection() { + return collection; + } + + public void setCollection(boolean collection) { + this.collection = collection; + } + + public List getResourceTypes() { + return resourceTypes; + } + + public Set getPropertyNames(HttpStatus status) { + if (!propertyNames.containsKey(status)) + propertyNames.put(status, new TreeSet<>(DavXmlElement.QNAME_COMPARATOR)); + return propertyNames.get(status); + } + + public Set getStatuses() { + return propertyNames.keySet(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java b/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java new file mode 100644 index 000000000..06da6792c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java @@ -0,0 +1,102 @@ +package org.argeo.cms.dav; + +import java.util.Comparator; +import java.util.Objects; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.argeo.api.acr.QNamed; + +enum DavXmlElement implements QNamed { + response, // + multistatus, // + href, // + /** MUST be the same as DName.collection */ + collection, // + prop, // + resourcetype, // + + // propfind + propfind, // + allprop, // + propname, // + include, // + propstat, // + status, // + + // locking + lockscope, // + locktype, // + supportedlock, // + lockentry, // + lockdiscovery, // + write, // + shared, // + exclusive, // + ; + + final static String WEBDAV_NAMESPACE_URI = "DAV:"; + final static String WEBDAV_DEFAULT_PREFIX = "D"; + + final static Comparator QNAME_COMPARATOR = new Comparator() { + + @Override + public int compare(QName qn1, QName qn2) { + if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace + return qn1.getLocalPart().compareTo(qn2.getLocalPart()); + } else { + return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI()); + } + } + + }; + +// private final QName value; +// +// private DavXmlElement() { +// this.value = new ContentName(getNamespace(), localName(), RuntimeNamespaceContext.getNamespaceContext()); +// } +// +// @Override +// public QName qName() { +// return value; +// } + + @Override + public String getNamespace() { + return WEBDAV_NAMESPACE_URI; + } + + @Override + public String getDefaultPrefix() { + return WEBDAV_DEFAULT_PREFIX; + } + + public static DavXmlElement toEnum(QName name) { + for (DavXmlElement e : values()) { + if (e.qName().equals(name)) + return e; + } + return null; + } + + public void setSimpleValue(XMLStreamWriter xsWriter, String value) throws XMLStreamException { + if (value == null) { + emptyElement(xsWriter); + return; + } + startElement(xsWriter); + xsWriter.writeCharacters(value); + xsWriter.writeEndElement(); + } + + public void emptyElement(XMLStreamWriter xsWriter) throws XMLStreamException { + xsWriter.writeEmptyElement(WEBDAV_NAMESPACE_URI, name()); + } + + public void startElement(XMLStreamWriter xsWriter) throws XMLStreamException { + xsWriter.writeStartElement(WEBDAV_NAMESPACE_URI, name()); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java new file mode 100644 index 000000000..c7b54b027 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java @@ -0,0 +1,213 @@ +package org.argeo.cms.dav; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.xml.namespace.QName; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.argeo.cms.http.HttpStatus; + +/** + * Asynchronously iterate over the response statuses of the response to a + * PROPFIND request. + */ +class MultiStatusReader implements Iterator { + private CompletableFuture empty = new CompletableFuture(); + private AtomicBoolean processed = new AtomicBoolean(false); + + private BlockingQueue queue = new ArrayBlockingQueue<>(64); + + private final String ignoredHref; + + public MultiStatusReader(InputStream in) { + this(in, null); + } + + /** Typically ignoring self */ + public MultiStatusReader(InputStream in, String ignoredHref) { + this.ignoredHref = ignoredHref; + ForkJoinPool.commonPool().execute(() -> process(in)); + } + + protected void process(InputStream in) { + try { + XMLInputFactory inputFactory = XMLInputFactory.newFactory(); + XMLStreamReader reader = inputFactory.createXMLStreamReader(in, StandardCharsets.UTF_8.name()); + + DavResponse currentResponse = null; + boolean collectiongProperties = false; + Set currentPropertyNames = null; + HttpStatus currentStatus = null; + + final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation + elements: while (reader.hasNext()) { + reader.next(); + if (reader.isStartElement()) { + QName name = reader.getName(); +// System.out.println(name); + DavXmlElement davXmlElement = DavXmlElement.toEnum(name); + if (davXmlElement != null) { + switch (davXmlElement) { + case response: + currentResponse = new DavResponse(); + break; + case href: + assert currentResponse != null; + while (reader.hasNext() && !reader.hasText()) + reader.next(); + String href = reader.getText(); + currentResponse.setHref(href); + break; +// case collection: +// currentResponse.setCollection(true); +// break; + case status: + reader.next(); + String statusLine = reader.getText(); + currentStatus = HttpStatus.parseStatusLine(statusLine); + break; + case prop: + collectiongProperties = true; + currentPropertyNames = new HashSet<>(); + break; + case resourcetype: + while (reader.hasNext()) { + int event = reader.nextTag(); + QName resourceType = reader.getName(); + if (event == XMLStreamConstants.END_ELEMENT && name.equals(resourceType)) + break; + assert currentResponse != null; + if (event == XMLStreamConstants.START_ELEMENT) { + if (COLLECTION.equals(resourceType)) + currentResponse.setCollection(true); + else + currentResponse.getResourceTypes().add(resourceType); + } + } + break; + default: + // ignore + } + } else { + if (collectiongProperties) { + String value = null; + // TODO deal with complex properties + readProperty: while (reader.hasNext()) { + reader.next(); + if (reader.getEventType() == XMLStreamConstants.END_ELEMENT) + break readProperty; + if (reader.getEventType() == XMLStreamConstants.CHARACTERS) + value = reader.getText(); + } + + if (name.getNamespaceURI().equals(DavResponse.MOD_DAV_NAMESPACE)) + continue elements; // skip mod_dav properties + + assert currentResponse != null; + currentPropertyNames.add(name); + if (value != null) + currentResponse.getProperties().put(name, value); + + } + } + } else if (reader.isEndElement()) { + QName name = reader.getName(); +// System.out.println(name); + DavXmlElement davXmlElement = DavXmlElement.toEnum(name); + if (davXmlElement != null) + switch (davXmlElement) { + case propstat: + currentResponse.getPropertyNames(currentStatus).addAll(currentPropertyNames); + currentPropertyNames = null; + break; + case response: + assert currentResponse != null; + if (ignoredHref == null || !ignoredHref.equals(currentResponse.getHref())) { + if (!empty.isDone()) + empty.complete(false); + publish(currentResponse); + } + case prop: + collectiongProperties = false; + break; + default: + // ignore + } + } + } + + if (!empty.isDone()) + empty.complete(true); + } catch (FactoryConfigurationError | XMLStreamException e) { + empty.completeExceptionally(e); + throw new IllegalStateException("Cannot process DAV response", e); + } finally { + processed(); + } + } + + protected synchronized void publish(DavResponse response) { + try { + queue.put(response); + } catch (InterruptedException e) { + throw new IllegalStateException("Cannot put response " + response, e); + } finally { + notifyAll(); + } + } + + protected synchronized void processed() { + processed.set(true); + notifyAll(); + } + + @Override + public synchronized boolean hasNext() { + try { + if (empty.get()) + return false; + while (!processed.get() && queue.isEmpty()) { + wait(); + } + if (!queue.isEmpty()) + return true; + if (processed.get()) + return false; + throw new IllegalStateException("Cannot determine hasNext"); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot determine hasNext", e); + } finally { + // notifyAll(); + } + } + + @Override + public synchronized DavResponse next() { + try { + if (!hasNext()) + throw new IllegalStateException("No fursther items are available"); + + DavResponse response = queue.take(); + return response; + } catch (InterruptedException e) { + throw new IllegalStateException("Cannot get next", e); + } finally { + // notifyAll(); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java new file mode 100644 index 000000000..4689b8c8d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java @@ -0,0 +1,167 @@ +package org.argeo.cms.dav; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.argeo.cms.http.HttpStatus; + +class MultiStatusWriter implements Consumer { + private BlockingQueue queue = new ArrayBlockingQueue<>(64); + +// private OutputStream out; + + private Thread processingThread; + + private AtomicBoolean done = new AtomicBoolean(false); + + private AtomicBoolean polling = new AtomicBoolean(); + + private String protocol; + + public MultiStatusWriter(String protocol) { + this.protocol = protocol; + } + + public void process(NamespaceContext namespaceContext, OutputStream out, CompletionStage published, + boolean propname) throws IOException { + published.thenRun(() -> allPublished()); + processingThread = Thread.currentThread(); +// this.out = out; + + try { + XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); + XMLStreamWriter xsWriter = xmlOutputFactory.createXMLStreamWriter(out, StandardCharsets.UTF_8.name()); + xsWriter.setNamespaceContext(namespaceContext); + xsWriter.setDefaultNamespace(DavXmlElement.WEBDAV_NAMESPACE_URI); + + xsWriter.writeStartDocument(); + DavXmlElement.multistatus.startElement(xsWriter); + xsWriter.writeDefaultNamespace(DavXmlElement.WEBDAV_NAMESPACE_URI); + + poll: while (!(done.get() && queue.isEmpty())) { + DavResponse davResponse; + try { + polling.set(true); + davResponse = queue.poll(10, TimeUnit.MILLISECONDS); + if (davResponse == null) + continue poll; + //System.err.println(davResponse.getHref()); + } catch (InterruptedException e) { + //System.err.println(e); + continue poll; + } finally { + polling.set(false); + } + + writeDavResponse(xsWriter, davResponse, propname); + } + + xsWriter.writeEndElement();// multistatus + xsWriter.writeEndDocument(); + xsWriter.close(); + out.close(); + } catch (FactoryConfigurationError | XMLStreamException e) { + synchronized (this) { + processingThread = null; + } + } + } + + protected void writeDavResponse(XMLStreamWriter xsWriter, DavResponse davResponse, boolean propname) + throws XMLStreamException { + Set namespaces = new HashSet<>(); + for (HttpStatus status : davResponse.getStatuses()) + for (QName key : davResponse.getPropertyNames(status)) { + if (key.getNamespaceURI().equals(DavXmlElement.WEBDAV_NAMESPACE_URI)) + continue; // skip + if (key.getNamespaceURI().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) + continue; // skip + namespaces.add(key.getNamespaceURI()); + } + DavXmlElement.response.startElement(xsWriter); + // namespaces + for (String ns : namespaces) + xsWriter.writeNamespace(xsWriter.getNamespaceContext().getPrefix(ns), ns); + + DavXmlElement.href.setSimpleValue(xsWriter, davResponse.getHref()); + + { + for (HttpStatus status : davResponse.getStatuses()) { + DavXmlElement.propstat.startElement(xsWriter); + { + DavXmlElement.prop.startElement(xsWriter); + + // resourcetype + if (HttpStatus.OK.equals(status)) + if (propname) { + DavXmlElement.resourcetype.emptyElement(xsWriter); + } else { + if (!davResponse.getResourceTypes().isEmpty() || davResponse.isCollection()) { + DavXmlElement.resourcetype.startElement(xsWriter); + if (davResponse.isCollection()) + DavXmlElement.collection.emptyElement(xsWriter); + for (QName resourceType : davResponse.getResourceTypes()) { + xsWriter.writeEmptyElement(resourceType.getNamespaceURI(), + resourceType.getLocalPart()); + } + xsWriter.writeEndElement();// resource type + } + } + + properties: for (QName key : davResponse.getPropertyNames(status)) { + if (DavXmlElement.resourcetype.qName().equals(key)) + continue properties; + + if (propname) { + xsWriter.writeEmptyElement(key.getNamespaceURI(), key.getLocalPart()); + } else { + xsWriter.writeStartElement(key.getNamespaceURI(), key.getLocalPart()); + xsWriter.writeCData(davResponse.getProperties().get(key)); + xsWriter.writeEndElement(); + } + } + xsWriter.writeEndElement();// prop + } + DavXmlElement.status.setSimpleValue(xsWriter, status.getStatusLine(protocol)); + xsWriter.writeEndElement();// propstat + } + } + xsWriter.writeEndElement();// response + } + + @Override + public void accept(DavResponse davResponse) { + try { + queue.put(davResponse); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + protected synchronized void allPublished() { + done.set(true); + if (processingThread != null && queue.isEmpty() && polling.get()) { + // we only interrupt if the queue is already processed + // so as not to interrupt I/O + processingThread.interrupt(); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java new file mode 100644 index 000000000..5dffcb63a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java @@ -0,0 +1,582 @@ +package org.argeo.cms.directory.ldap; + +import static org.argeo.cms.directory.ldap.LdapNameUtils.toLdapName; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.StringJoiner; + +import javax.naming.Context; +import javax.naming.InvalidNameException; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.transaction.xa.XAResource; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.ldap.LdapObj; +import org.argeo.api.cms.directory.CmsDirectory; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.transaction.WorkControl; +import org.argeo.api.cms.transaction.WorkingCopyXaResource; +import org.argeo.api.cms.transaction.XAResourceProvider; +import org.argeo.cms.osgi.useradmin.OsUserDirectory; +import org.argeo.cms.runtime.DirectoryConf; + +/** A {@link CmsDirectory} based either on LDAP or LDIF. */ +public abstract class AbstractLdapDirectory implements CmsDirectory, XAResourceProvider { + protected static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name"; + protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password"; + + private final LdapName baseDn; + private final Hashtable configProperties; + private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn; + private final String userObjectClass, groupObjectClass; + private String memberAttributeId = "member"; + + private final boolean readOnly; + private final boolean disabled; + private final String uri; + + private String forcedPassword; + + private final boolean scoped; + + private List credentialAttributeIds = Arrays + .asList(new String[] { LdapAttr.userPassword.name(), LdapAttr.authPassword.name() }); + + private WorkControl transactionControl; + private WorkingCopyXaResource xaResource; + + private LdapDirectoryDao directoryDao; + + /** Whether the the directory has is authenticated via a service user. */ + private boolean authenticated = false; + + public AbstractLdapDirectory(URI uriArg, Dictionary props, boolean scoped) { + this.configProperties = new Hashtable(); + for (Enumeration keys = props.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + configProperties.put(key, props.get(key)); + } + + String baseDnStr = DirectoryConf.baseDn.getValue(configProperties); + if (baseDnStr == null) + throw new IllegalArgumentException("Base DN must be specified: " + configProperties); + baseDn = toLdapName(baseDnStr); + this.scoped = scoped; + + if (uriArg != null) { + uri = uriArg.toString(); + // uri from properties is ignored + } else { + String uriStr = DirectoryConf.uri.getValue(configProperties); + if (uriStr == null) + uri = null; + else + uri = uriStr; + } + + forcedPassword = DirectoryConf.forcedPassword.getValue(configProperties); + + userObjectClass = DirectoryConf.userObjectClass.getValue(configProperties); + groupObjectClass = DirectoryConf.groupObjectClass.getValue(configProperties); + + String userBase = DirectoryConf.userBase.getValue(configProperties); + String groupBase = DirectoryConf.groupBase.getValue(configProperties); + String systemRoleBase = DirectoryConf.systemRoleBase.getValue(configProperties); + try { +// baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties)); + userBaseRdn = new Rdn(userBase); +// userBaseDn = new LdapName(userBase + "," + baseDn); + groupBaseRdn = new Rdn(groupBase); +// groupBaseDn = new LdapName(groupBase + "," + baseDn); + systemRoleBaseRdn = new Rdn(systemRoleBase); + } catch (InvalidNameException e) { + throw new IllegalArgumentException( + "Badly formated base DN " + DirectoryConf.baseDn.getValue(configProperties), e); + } + + // read only + String readOnlyStr = DirectoryConf.readOnly.getValue(configProperties); + if (readOnlyStr == null) { + readOnly = readOnlyDefault(uri); + configProperties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly)); + } else + readOnly = Boolean.parseBoolean(readOnlyStr); + + // disabled + String disabledStr = DirectoryConf.disabled.getValue(configProperties); + if (disabledStr != null) + disabled = Boolean.parseBoolean(disabledStr); + else + disabled = false; + if (!getRealm().isEmpty()) { + // IPA multiple LDAP causes URI parsing to fail + // TODO manage generic redundant LDAP case + directoryDao = new LdapDao(this); + } else { + if (uri != null) { + URI u = URI.create(uri); + if (DirectoryConf.SCHEME_LDAP.equals(u.getScheme()) + || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) { + directoryDao = new LdapDao(this); + authenticated = configProperties.get(Context.SECURITY_PRINCIPAL) != null; + } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) { + directoryDao = new LdifDao(this); + authenticated = true; + } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) { + directoryDao = new OsUserDirectory(this); + authenticated = true; + // singleUser = true; + } else { + throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); + } + } else { + // in memory + directoryDao = new LdifDao(this); + } + } + if (directoryDao != null) + xaResource = new WorkingCopyXaResource<>(directoryDao); + } + + /* + * INITIALISATION + */ + + public void init() { + getDirectoryDao().init(); + } + + public void destroy() { + getDirectoryDao().destroy(); + } + + /* + * CREATION + */ + protected abstract LdapEntry newUser(LdapName name); + + protected abstract LdapEntry newGroup(LdapName name); + + /* + * EDITION + */ + + public boolean isEditing() { + return xaResource.wc() != null; + } + + public LdapEntryWorkingCopy getWorkingCopy() { + LdapEntryWorkingCopy wc = xaResource.wc(); + if (wc == null) + return null; + return wc; + } + + public void checkEdit() { + if (xaResource.wc() == null) { + try { + transactionControl.getWorkContext().registerXAResource(xaResource, null); + } catch (Exception e) { + throw new IllegalStateException("Cannot enlist " + xaResource, e); + } + } else { + } + } + + public void setTransactionControl(WorkControl transactionControl) { + this.transactionControl = transactionControl; + } + + public XAResource getXaResource() { + return xaResource; + } + + public boolean removeEntry(LdapName dn) { + checkEdit(); + LdapEntryWorkingCopy wc = getWorkingCopy(); + boolean actuallyDeleted; + if (getDirectoryDao().entryExists(dn) || wc.getNewData().containsKey(dn)) { + LdapEntry user = doGetRole(dn); + wc.getDeletedData().put(dn, user); + actuallyDeleted = true; + } else {// just removing from groups (e.g. system roles) + actuallyDeleted = false; + } + for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) { + LdapEntry group = doGetRole(groupDn); + group.getAttributes().get(getMemberAttributeId()).remove(dn.toString()); + } + return actuallyDeleted; + } + + /* + * RETRIEVAL + */ + + protected LdapEntry doGetRole(LdapName dn) { + LdapEntryWorkingCopy wc = getWorkingCopy(); + LdapEntry user; + try { + user = getDirectoryDao().doGetEntry(dn); + } catch (NameNotFoundException e) { + user = null; + } + if (wc != null) { + if (user == null && wc.getNewData().containsKey(dn)) + user = wc.getNewData().get(dn); + else if (wc.getDeletedData().containsKey(dn)) + user = null; + } + return user; + } + + protected void collectGroups(LdapEntry user, List allRoles) { + Attributes attrs = user.getAttributes(); + // TODO centralize attribute name + Attribute memberOf = attrs.get(LdapAttr.memberOf.name()); + // if user belongs to this directory, we only check memberOf + if (memberOf != null && user.getDn().startsWith(getBaseDn())) { + try { + NamingEnumeration values = memberOf.getAll(); + while (values.hasMore()) { + Object value = values.next(); + LdapName groupDn = new LdapName(value.toString()); + LdapEntry group = doGetRole(groupDn); + if (group != null) { + allRoles.add(group); + } else { + // user doesn't have the right to retrieve role, but we know it exists + // otherwise memberOf would not work + group = newGroup(groupDn); + allRoles.add(group); + } + } + } catch (NamingException e) { + throw new IllegalStateException("Cannot get memberOf groups for " + user, e); + } + } else { + directGroups: for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) { + LdapEntry group = doGetRole(groupDn); + if (group != null) { + if (allRoles.contains(group)) { + // important in order to avoi loops + continue directGroups; + } + allRoles.add(group); + collectGroups(group, allRoles); + } + } + } + } + + /* + * HIERARCHY + */ + @Override + public HierarchyUnit getHierarchyUnit(String path) { + LdapName dn = pathToName(path); + return directoryDao.doGetHierarchyUnit(dn); + } + + @Override + public Iterable getDirectHierarchyUnits(boolean functionalOnly) { + return directoryDao.doGetDirectHierarchyUnits(baseDn, functionalOnly); + } + + @Override + public HierarchyUnit getDirectChild(Type type) { + // TODO factorise with hierarchy unit? + return switch (type) { + case ROLES -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getSystemRoleBaseRdn())); + case PEOPLE -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getUserBaseRdn())); + case GROUPS -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getGroupBaseRdn())); + case FUNCTIONAL -> throw new IllegalArgumentException("Type must be a technical type"); + }; + } + + @Override + public String getHierarchyUnitName() { + return getName(); + } + + @Override + public String getHierarchyUnitLabel(Locale locale) { + String key = LdapNameUtils.getLastRdn(getBaseDn()).getType(); + Object value = LdapEntry.getLocalized(asLdapEntry().getProperties(), key, locale); + if (value == null) + value = getHierarchyUnitName(); + assert value != null; + return value.toString(); + } + + @Override + public HierarchyUnit getParent() { + return null; + } + + @Override + public boolean isType(Type type) { + return Type.FUNCTIONAL.equals(type); + } + + @Override + public CmsDirectory getDirectory() { + return this; + } + + @Override + public HierarchyUnit createHierarchyUnit(String path) { + checkEdit(); + LdapEntryWorkingCopy wc = getWorkingCopy(); + LdapName dn = pathToName(path); + if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn)) + || wc.getNewData().containsKey(dn)) + throw new IllegalArgumentException("Already a hierarchy unit " + path); + BasicAttributes attrs = new BasicAttributes(true); + attrs.put(LdapAttr.objectClass.name(), LdapObj.organizationalUnit.name()); + Rdn nameRdn = dn.getRdn(dn.size() - 1); + // TODO deal with multiple attr RDN + attrs.put(nameRdn.getType(), nameRdn.getValue()); + wc.getModifiedData().put(dn, attrs); + LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn); + wc.getNewData().put(dn, newHierarchyUnit); + return newHierarchyUnit; + } + + /* + * PATHS + */ + + @Override + public String getBase() { + return getBaseDn().toString(); + } + + @Override + public String getName() { + return nameToSimple(getBaseDn(), "."); + } + + protected String nameToRelativePath(LdapName dn) { + LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn); + return nameToSimple(name, "/"); + } + + protected String nameToSimple(LdapName name, String separator) { + StringJoiner path = new StringJoiner(separator); + for (int i = 0; i < name.size(); i++) { + path.add(name.getRdn(i).getValue().toString()); + } + return path.toString(); + + } + + protected LdapName pathToName(String path) { + try { + LdapName name = (LdapName) getBaseDn().clone(); + String[] segments = path.split("/"); + Rdn parentRdn = null; + // segments[0] is the directory itself + for (int i = 0; i < segments.length; i++) { + String segment = segments[i]; + // TODO make attr names configurable ? + String attr = getDirectory().getRealm().isPresent()/* IPA */ ? LdapAttr.cn.name() : LdapAttr.ou.name(); + if (parentRdn != null) { + if (getUserBaseRdn().equals(parentRdn)) + attr = LdapAttr.uid.name(); + else if (getGroupBaseRdn().equals(parentRdn)) + attr = LdapAttr.cn.name(); + else if (getSystemRoleBaseRdn().equals(parentRdn)) + attr = LdapAttr.cn.name(); + } + Rdn rdn = new Rdn(attr, segment); + name.add(rdn); + parentRdn = rdn; + } + return name; + } catch (InvalidNameException e) { + throw new IllegalStateException("Cannot convert " + path + " to LDAP name", e); + } + + } + + /* + * UTILITIES + */ + protected boolean isExternal(LdapName name) { + return !name.startsWith(baseDn); + } + + protected static boolean hasObjectClass(Attributes attrs, LdapObj objectClass) { + return hasObjectClass(attrs, objectClass.name()); + } + + protected static boolean hasObjectClass(Attributes attrs, String objectClass) { + try { + Attribute attr = attrs.get(LdapAttr.objectClass.name()); + NamingEnumeration en = attr.getAll(); + while (en.hasMore()) { + String v = en.next().toString(); + if (v.equalsIgnoreCase(objectClass)) + return true; + + } + return false; + } catch (NamingException e) { + throw new IllegalStateException("Cannot search for objectClass " + objectClass, e); + } + } + + private static boolean readOnlyDefault(String uriStr) { + if (uriStr == null) + return true; + /// TODO make it more generic + URI uri; + try { + uri = new URI(uriStr.split(" ")[0]); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + if (uri.getScheme() == null) + return false;// assume relative file to be writable + if (uri.getScheme().equals(DirectoryConf.SCHEME_FILE)) { + File file = new File(uri); + if (file.exists()) + return !file.canWrite(); + else + return !file.getParentFile().canWrite(); + } else if (uri.getScheme().equals(DirectoryConf.SCHEME_LDAP)) { + if (uri.getAuthority() != null)// assume writable if authenticated + return false; + } else if (uri.getScheme().equals(DirectoryConf.SCHEME_OS)) { + return true; + } + return true;// read only by default + } + + /* + * AS AN ENTRY + */ + public LdapEntry asLdapEntry() { + try { + return directoryDao.doGetEntry(baseDn); + } catch (NameNotFoundException e) { + throw new IllegalStateException("Cannot get " + baseDn + " entry", e); + } + } + + public Dictionary getProperties() { + return asLdapEntry().getProperties(); + } + + /* + * ACCESSORS + */ + @Override + public Optional getRealm() { + Object realm = configProperties.get(DirectoryConf.realm.name()); + if (realm == null) + return Optional.empty(); + return Optional.of(realm.toString()); + } + + public LdapName getBaseDn() { + return (LdapName) baseDn.clone(); + } + + public boolean isReadOnly() { + return readOnly; + } + + public boolean isDisabled() { + return disabled; + } + + public boolean isAuthenticated() { + return authenticated; + } + + public Rdn getUserBaseRdn() { + return userBaseRdn; + } + + public Rdn getGroupBaseRdn() { + return groupBaseRdn; + } + + public Rdn getSystemRoleBaseRdn() { + return systemRoleBaseRdn; + } + +// public Dictionary getConfigProperties() { +// return configProperties; +// } + + public Dictionary cloneConfigProperties() { + return new Hashtable<>(configProperties); + } + + public String getForcedPassword() { + return forcedPassword; + } + + public boolean isScoped() { + return scoped; + } + + public List getCredentialAttributeIds() { + return credentialAttributeIds; + } + + public String getUri() { + return uri; + } + + public LdapDirectoryDao getDirectoryDao() { + return directoryDao; + } + + /** dn can be null, in that case a default should be returned. */ + public String getUserObjectClass() { + return userObjectClass; + } + + public String getGroupObjectClass() { + return groupObjectClass; + } + + public String getMemberAttributeId() { + return memberAttributeId; + } + + /* + * OBJECT METHODS + */ + + @Override + public int hashCode() { + return baseDn.hashCode(); + } + + @Override + public String toString() { + return "Directory " + baseDn.toString(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectoryDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectoryDao.java new file mode 100644 index 000000000..c4a691032 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectoryDao.java @@ -0,0 +1,33 @@ +package org.argeo.cms.directory.ldap; + +import javax.naming.ldap.LdapName; + +/** Base class for LDAP/LDIF directory DAOs. */ +public abstract class AbstractLdapDirectoryDao implements LdapDirectoryDao { + + private AbstractLdapDirectory directory; + + public AbstractLdapDirectoryDao(AbstractLdapDirectory directory) { + this.directory = directory; + } + + public AbstractLdapDirectory getDirectory() { + return directory; + } + + @Override + public LdapEntryWorkingCopy newWorkingCopy() { + return new LdapEntryWorkingCopy(); + } + + @Override + public LdapEntry newUser(LdapName name) { + return getDirectory().newUser(name); + } + + @Override + public LdapEntry newGroup(LdapName name) { + return getDirectory().newGroup(name); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AttributesDictionary.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AttributesDictionary.java new file mode 100644 index 000000000..9deda1be4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AttributesDictionary.java @@ -0,0 +1,171 @@ +package org.argeo.cms.directory.ldap; + +import java.util.Dictionary; +import java.util.Enumeration; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; + +public class AttributesDictionary extends Dictionary { + 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 keys() { + NamingEnumeration namingEnumeration = attributes.getIDs(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return namingEnumeration.hasMoreElements(); + } + + @Override + public String nextElement() { + return namingEnumeration.nextElement(); + } + + }; + } + + @Override + public Enumeration elements() { + NamingEnumeration namingEnumeration = attributes.getIDs(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return namingEnumeration.hasMoreElements(); + } + + @Override + public Object nextElement() { + String key = namingEnumeration.nextElement(); + return get(key); + } + + }; + } + + @Override + /** @returns a String or String[] */ + 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 content of an {@link Attributes} to the provided + * {@link Dictionary}. + */ + public static void copy(Attributes attributes, Dictionary dictionary) { + AttributesDictionary ad = new AttributesDictionary(attributes); + Enumeration 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 dictionary, Attributes attributes) { + AttributesDictionary ad = new AttributesDictionary(attributes); + Enumeration keys = dictionary.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + ad.put(key, dictionary.get(key)); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AuthPassword.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AuthPassword.java new file mode 100644 index 000000000..a871912e1 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AuthPassword.java @@ -0,0 +1,140 @@ +package org.argeo.cms.directory.ldap; + +import java.io.IOException; +import java.util.Arrays; +import java.util.StringTokenizer; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.argeo.api.acr.ldap.LdapAttr; + +/** LDAP authPassword field according to RFC 3112 */ +public class AuthPassword implements CallbackHandler { + private final String authScheme; + private final String authInfo; + private final String authValue; + + public AuthPassword(String value) { + StringTokenizer st = new StringTokenizer(value, "$"); + // TODO make it more robust, deal with bad formatting + this.authScheme = st.nextToken().trim(); + this.authInfo = st.nextToken().trim(); + this.authValue = st.nextToken().trim(); + + String expectedAuthScheme = getExpectedAuthScheme(); + if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme)) + throw new IllegalArgumentException( + "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme); + } + + protected AuthPassword(String authInfo, String authValue) { + this.authScheme = getExpectedAuthScheme(); + if (authScheme == null) + throw new IllegalArgumentException("Expected auth scheme cannot be null"); + this.authInfo = authInfo; + this.authValue = authValue; + } + + protected AuthPassword(AuthPassword authPassword) { + this.authScheme = authPassword.getAuthScheme(); + this.authInfo = authPassword.getAuthInfo(); + this.authValue = authPassword.getAuthValue(); + } + + protected String getExpectedAuthScheme() { + return null; + } + + protected boolean matchAuthValue(Object object) { + return authValue.equals(object.toString()); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AuthPassword)) + return false; + AuthPassword authPassword = (AuthPassword) obj; + return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo) + && authValue.equals(authValue); + } + + public boolean keyEquals(AuthPassword authPassword) { + return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo); + } + + @Override + public int hashCode() { + return authValue.hashCode(); + } + + @Override + public String toString() { + return toAuthPassword(); + } + + public final String toAuthPassword() { + return getAuthScheme() + '$' + authInfo + '$' + authValue; + } + + public String getAuthScheme() { + return authScheme; + } + + public String getAuthInfo() { + return authInfo; + } + + public String getAuthValue() { + return authValue; + } + + public static AuthPassword matchAuthValue(Attributes attributes, char[] value) { + try { + Attribute authPassword = attributes.get(LdapAttr.authPassword.name()); + if (authPassword != null) { + NamingEnumeration values = authPassword.getAll(); + while (values.hasMore()) { + Object val = values.next(); + AuthPassword token = new AuthPassword(val.toString()); + String auth; + if (Arrays.binarySearch(value, '$') >= 0) { + auth = token.authInfo + '$' + token.authValue; + } else { + auth = token.authValue; + } + if (Arrays.equals(auth.toCharArray(), value)) + return token; + // if (token.matchAuthValue(value)) + // return token; + } + } + return null; + } catch (NamingException e) { + throw new IllegalStateException("Cannot check attribute", e); + } + } + + public static boolean remove(Attributes attributes, AuthPassword value) { + Attribute authPassword = attributes.get(LdapAttr.authPassword.name()); + return authPassword.remove(value.toAuthPassword()); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) + ((NameCallback) callback).setName(toAuthPassword()); + else if (callback instanceof PasswordCallback) + ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray()); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java new file mode 100644 index 000000000..94e0ac46d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java @@ -0,0 +1,482 @@ +package org.argeo.cms.directory.ldap; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.ldap.LdapName; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.ldap.LdapObj; +import org.argeo.api.cms.directory.DirectoryDigestUtils; + +/** An entry in an LDAP (or LDIF) directory. */ +public class DefaultLdapEntry implements LdapEntry { + private final AbstractLdapDirectory directory; + + private final LdapName dn; + + private AttributeDictionary properties; + private AttributeDictionary credentials; + +// private String primaryObjectClass; +// private List objectClasses = new ArrayList<>(); + + protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn) { + Objects.requireNonNull(directory); + Objects.requireNonNull(dn); + this.directory = directory; + this.dn = dn; + + // Object classes +// Objects.requireNonNull(initialAttributes); +// try { +// NamingEnumeration en = initialAttributes.get(LdapAttrs.objectClass.name()).getAll(); +// String first = null; +// attrs: while (en.hasMore()) { +// String v = en.next().toString(); +// if (v.equalsIgnoreCase(LdapObjs.top.name())) +// continue attrs; +// if (first == null) +// first = v; +// if (v.equalsIgnoreCase(getDirectory().getUserObjectClass())) +// primaryObjectClass = getDirectory().getUserObjectClass(); +// else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass())) +// primaryObjectClass = getDirectory().getGroupObjectClass(); +// objectClasses.add(v); +// } +// if (primaryObjectClass == null) { +// if (first == null) +// throw new IllegalStateException("Could not find primary object class"); +// primaryObjectClass = first; +// } +// } catch (NamingException e) { +// throw new IllegalStateException("Cannot find object classes", e); +// } + + } + + @Override + public LdapName getDn() { + // always return a copy since LdapName is mutable + return (LdapName) dn.clone(); + } + + public synchronized Attributes getAttributes() { + return isEditing() ? getModifiedAttributes() : getDirectory().getDirectoryDao().doGetAttributes(dn); + } + + @Override + public List getReferences(String attributeId) { + Attribute memberAttribute = getAttributes().get(attributeId); + if (memberAttribute == null) + return new ArrayList(); + try { + List roles = new ArrayList(); + NamingEnumeration values = memberAttribute.getAll(); + while (values.hasMore()) { + LdapName dn = new LdapName(values.next().toString()); + roles.add(dn); + } + return roles; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get members", e); + } + + } + + /** Should only be called from working copy thread. */ + protected synchronized Attributes getModifiedAttributes() { + assert getWc() != null; + return getWc().getModifiedData().get(getDn()); + } + + protected synchronized boolean isEditing() { + return getWc() != null && getModifiedAttributes() != null; + } + + private synchronized LdapEntryWorkingCopy getWc() { + return directory.getWorkingCopy(); + } + + protected synchronized void startEditing() { +// if (frozen) +// throw new IllegalStateException("Cannot edit frozen view"); + if (directory.isReadOnly()) + throw new IllegalStateException("User directory is read-only"); + assert getModifiedAttributes() == null; + getWc().startEditing(this); + // modifiedAttributes = (Attributes) publishedAttributes.clone(); + } + + public synchronized void publishAttributes(Attributes modifiedAttributes) { +// publishedAttributes = modifiedAttributes; + } + + /* + * PROPERTIES + */ + @Override + public Dictionary getProperties() { + if (properties == null) + properties = new AttributeDictionary(false); + return properties; + } + + public Dictionary getCredentials() { + if (credentials == null) + credentials = new AttributeDictionary(true); + return credentials; + } + + /* + * CREDENTIALS + */ + @Override + public boolean hasCredential(String key, Object value) { + if (key == null) { + // TODO check other sources (like PKCS12) + // String pwd = new String((char[]) value); + // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112) + char[] password = DirectoryDigestUtils.bytesToChars(value); + + if (getDirectory().getForcedPassword() != null + && getDirectory().getForcedPassword().equals(new String(password))) + return true; + + AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password); + if (authPassword != null) { + if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) { + SharedSecret onceToken = new SharedSecret(authPassword); + if (onceToken.isExpired()) { + // AuthPassword.remove(getAttributes(), onceToken); + return false; + } else { + // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken); + return true; + } + // TODO delete expired tokens? + } else { + // TODO implement SHA + throw new UnsupportedOperationException( + "Unsupported authPassword scheme " + authPassword.getAuthScheme()); + } + } + + // Regular password +// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256); + if (hasCredential(LdapAttr.userPassword.name(), DirectoryDigestUtils.charsToBytes(password))) + return true; + return false; + } + + Object storedValue = getCredentials().get(key); + if (storedValue == null || value == null) + return false; + if (!(value instanceof String || value instanceof byte[])) + return false; + if (storedValue instanceof String && value instanceof String) + return storedValue.equals(value); + if (storedValue instanceof byte[] && value instanceof byte[]) { + String storedBase64 = new String((byte[]) storedValue, US_ASCII); + String passwordScheme = null; + if (storedBase64.charAt(0) == '{') { + int index = storedBase64.indexOf('}'); + if (index > 0) { + passwordScheme = storedBase64.substring(1, index); + String storedValueBase64 = storedBase64.substring(index + 1); + byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64); + char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value); + byte[] valueBytes; + if (DirectoryDigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) { + valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, + null); + } else if (DirectoryDigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) { + // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/ + byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4); + BigInteger iterations = new BigInteger(iterationsArr); + byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length, + iterationsArr.length + 64); + byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length, + storedValueBytes.length); + int keyLengthBits = keyArr.length * 8; + valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt, + iterations.intValue(), keyLengthBits); + } else { + throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme); + } + return Arrays.equals(storedValueBytes, valueBytes); + } + } + } +// if (storedValue instanceof byte[] && value instanceof byte[]) { +// return Arrays.equals((byte[]) storedValue, (byte[]) value); +// } + return false; + } + + /** Hash the password */ + private static byte[] sha1hash(char[] password) { + byte[] hashedPassword = ("{SHA}" + Base64.getEncoder() + .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password)))) + .getBytes(StandardCharsets.UTF_8); + return hashedPassword; + } + + public AbstractLdapDirectory getDirectory() { + return directory; + } + + public LdapDirectoryDao getDirectoryDao() { + return directory.getDirectoryDao(); + } + + @Override + public int hashCode() { + return dn.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj instanceof LdapEntry) { + LdapEntry that = (LdapEntry) obj; + return this.dn.equals(that.getDn()); + } + return false; + } + + @Override + public String toString() { + return dn.toString(); + } + + private static boolean isAsciiPrintable(String str) { + if (str == null) { + return false; + } + int sz = str.length(); + for (int i = 0; i < sz; i++) { + if (isAsciiPrintable(str.charAt(i)) == false) { + return false; + } + } + return true; + } + + private static boolean isAsciiPrintable(char ch) { + return ch >= 32 && ch < 127; + } + + protected class AttributeDictionary extends Dictionary { + private final List effectiveKeys = new ArrayList(); + private final List attrFilter; + private final Boolean includeFilter; + + public AttributeDictionary(Boolean credentials) { + this.attrFilter = getDirectory().getCredentialAttributeIds(); + this.includeFilter = credentials; + try { + NamingEnumeration ids = getAttributes().getIDs(); + while (ids.hasMore()) { + String id = ids.next(); + if (credentials && attrFilter.contains(id)) + effectiveKeys.add(id); + else if (!credentials && !attrFilter.contains(id)) + effectiveKeys.add(id); + } + } catch (NamingException e) { + throw new IllegalStateException("Cannot initialise attribute dictionary", e); + } + if (!credentials) + effectiveKeys.add(LdapAttr.objectClasses.name()); + } + + @Override + public int size() { + return effectiveKeys.size(); + } + + @Override + public boolean isEmpty() { + return effectiveKeys.size() == 0; + } + + @Override + public Enumeration keys() { + return Collections.enumeration(effectiveKeys); + } + + @Override + public Enumeration elements() { + final Iterator it = effectiveKeys.iterator(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return it.hasNext(); + } + + @Override + public Object nextElement() { + String key = it.next(); + return get(key); + } + + }; + } + + @Override + public Object get(Object key) { + try { + Attribute attr = !key.equals(LdapAttr.objectClasses.name()) ? getAttributes().get(key.toString()) + : getAttributes().get(LdapAttr.objectClass.name()); + if (attr == null) + return null; + Object value = attr.get(); + if (value instanceof byte[]) { + if (key.equals(LdapAttr.userPassword.name())) + // TODO other cases (certificates, images) + return value; + value = new String((byte[]) value, StandardCharsets.UTF_8); + } + if (attr.size() == 1) + return value; + // special case for object class + if (key.equals(LdapAttr.objectClass.name())) { + // TODO support multiple object classes + NamingEnumeration en = attr.getAll(); + String first = null; + attrs: while (en.hasMore()) { + String v = en.next().toString(); + if (v.equalsIgnoreCase(LdapObj.top.name())) + continue attrs; + if (first == null) + first = v; + if (v.equalsIgnoreCase(getDirectory().getUserObjectClass())) + return getDirectory().getUserObjectClass(); + else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass())) + return getDirectory().getGroupObjectClass(); + } + if (first != null) + return first; + throw new IllegalStateException("Cannot find objectClass in " + value); + } else { + NamingEnumeration en = attr.getAll(); + StringJoiner values = new StringJoiner("\n"); + while (en.hasMore()) { + String v = en.next().toString(); + values.add(v); + } + return values.toString(); + } +// else +// return value; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get value for attribute " + key, e); + } + } + + @Override + public Object put(String key, Object value) { + Objects.requireNonNull(value, "Value for key " + key + " is null"); + try { + if (key == null) { + // FIXME remove this "feature", a key should be specified + // TODO persist to other sources (like PKCS12) + char[] password = DirectoryDigestUtils.bytesToChars(value); + byte[] hashedPassword = sha1hash(password); + return put(LdapAttr.userPassword.name(), hashedPassword); + } + if (key.startsWith("X-")) { + return put(LdapAttr.authPassword.name(), value); + } + + // start editing + getDirectory().checkEdit(); + if (!isEditing()) + startEditing(); + + // object classes special case. + if (key.equals(LdapAttr.objectClasses.name())) { + Attribute attribute = new BasicAttribute(LdapAttr.objectClass.name()); + String[] objectClasses = value.toString().split("\n"); + for (String objectClass : objectClasses) { + if (objectClass.trim().equals("")) + continue; + attribute.add(objectClass); + } + Attribute previousAttribute = getModifiedAttributes().put(attribute); + if (previousAttribute != null) + return previousAttribute.get(); + else + return null; + } + + if (!(value instanceof String || value instanceof byte[])) + throw new IllegalArgumentException("Value must be String or byte[]"); + + if (includeFilter && !attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + " not included"); + else if (!includeFilter && attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + " excluded"); + + Attribute attribute = getModifiedAttributes().get(key.toString()); + // if (attribute == null) // block unit tests + attribute = new BasicAttribute(key.toString()); + if (value instanceof String && !isAsciiPrintable(((String) value))) + attribute.add(((String) value).getBytes(StandardCharsets.UTF_8)); + else + attribute.add(value); + Attribute previousAttribute = getModifiedAttributes().put(attribute); + if (previousAttribute != null) + return previousAttribute.get(); + else + return null; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get value for attribute " + key, e); + } + } + + @Override + public Object remove(Object key) { + getDirectory().checkEdit(); + if (!isEditing()) + startEditing(); + + if (includeFilter && !attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + " not included"); + else if (!includeFilter && attrFilter.contains(key)) + throw new IllegalArgumentException("Key " + key + " excluded"); + + try { + Attribute attr = getModifiedAttributes().remove(key.toString()); + if (attr != null) + return attr.get(); + else + return null; + } catch (NamingException e) { + throw new IllegalStateException("Cannot remove attribute " + key, e); + } + } + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/IpaUtils.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/IpaUtils.java new file mode 100644 index 000000000..b14c090ab --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/IpaUtils.java @@ -0,0 +1,150 @@ +package org.argeo.cms.directory.ldap; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.StringJoiner; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.cms.dns.DnsBrowser; +import org.argeo.cms.runtime.DirectoryConf; + +/** Free IPA specific conventions. */ +public class IpaUtils { + public final static String IPA_USER_BASE = "cn=users"; + public final static String IPA_GROUP_BASE = "cn=groups"; + public final static String IPA_ROLE_BASE = "cn=roles"; + public final static String IPA_SERVICE_BASE = "cn=services"; + + public final static String IPA_ACCOUNTS_BASE = "cn=accounts"; + + private final static String KRB_PRINCIPAL_NAME = LdapAttr.krbPrincipalName.name().toLowerCase(); + + public final static String IPA_USER_DIRECTORY_CONFIG = DirectoryConf.userBase + "=" + IPA_USER_BASE + "&" + + DirectoryConf.groupBase + "=" + IPA_GROUP_BASE + "&" + DirectoryConf.systemRoleBase + "=" + IPA_ROLE_BASE + + "&" + DirectoryConf.readOnly + "=true"; + + @Deprecated + static String domainToUserDirectoryConfigPath(String realm) { + return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + DirectoryConf.realm.name() + "=" + realm; + } + + public static void addIpaConfig(String realm, Dictionary properties) { + properties.put(DirectoryConf.baseDn.name(), domainToBaseDn(realm)); + properties.put(DirectoryConf.realm.name(), realm); + properties.put(DirectoryConf.userBase.name(), IPA_USER_BASE); + properties.put(DirectoryConf.groupBase.name(), IPA_GROUP_BASE); + properties.put(DirectoryConf.systemRoleBase.name(), IPA_ROLE_BASE); + properties.put(DirectoryConf.readOnly.name(), Boolean.TRUE.toString()); + } + + public static String domainToBaseDn(String domain) { + String[] dcs = domain.split("\\."); + StringJoiner sj = new StringJoiner(","); + for (int i = 0; i < dcs.length; i++) { + String dc = dcs[i]; + sj.add(LdapAttr.dc.name() + '=' + dc.toLowerCase()); + } + return IPA_ACCOUNTS_BASE + ',' + sj.toString(); + } + + public static LdapName kerberosToDn(String kerberosName) { + String[] kname = kerberosName.split("@"); + String username = kname[0]; + String baseDn = domainToBaseDn(kname[1]); + String dn; + if (!username.contains("/")) + dn = LdapAttr.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn; + else + dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn; + try { + return new LdapName(dn); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn); + } + } + + private IpaUtils() { + + } + + public static String kerberosDomainFromDns() { + String kerberosDomain; + try (DnsBrowser dnsBrowser = new DnsBrowser()) { + // TODO retrieve hostname from CMS config + InetAddress localhost = InetAddress.getLocalHost(); + String hostname = localhost.getHostName(); + int dotIndex = hostname.indexOf('.'); + if (dotIndex <= 0) { + hostname = localhost.getCanonicalHostName(); + dotIndex = hostname.indexOf('.'); + if (dotIndex <= 0) + throw new IllegalArgumentException( + "Cannot extract DNS zone from hostname " + hostname + " (" + localhost + ")"); + } + String dnsZone = hostname.substring(dotIndex + 1); + kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT"); + return kerberosDomain; + } catch (IOException e) { + throw new IllegalStateException("Cannot determine Kerberos domain from DNS", e); + } + + } + + public static Dictionary convertIpaUri(URI uri) { + String path = uri.getPath(); + String kerberosRealm; + if (path == null || path.length() <= 1) { + kerberosRealm = kerberosDomainFromDns(); + } else { + kerberosRealm = path.substring(1); + } + + if (kerberosRealm == null) + throw new IllegalStateException("No Kerberos domain available for " + uri); + // TODO intergrate CA certificate in truststore + // String schemeToUse = SCHEME_LDAPS; + String schemeToUse = DirectoryConf.SCHEME_LDAP; + List ldapHosts; + String ldapHostsStr = uri.getHost(); + if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) { + try (DnsBrowser dnsBrowser = new DnsBrowser()) { + ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(), + schemeToUse.equals(DirectoryConf.SCHEME_LDAP) ? true : false); + if (ldapHosts == null || ldapHosts.size() == 0) { + throw new IllegalStateException("Cannot configure LDAP for IPA " + uri); + } else { + ldapHostsStr = ldapHosts.get(0); + } + } catch (IOException e) { + throw new IllegalStateException("Cannot convert IPA uri " + uri, e); + } + } else { + ldapHosts = new ArrayList<>(); + ldapHosts.add(ldapHostsStr); + } + + StringBuilder uriStr = new StringBuilder(); + try { + for (String host : ldapHosts) { + URI convertedUri = new URI(schemeToUse + "://" + host + "/"); + uriStr.append(convertedUri).append(' '); + } + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot convert IPA uri " + uri, e); + } + + Hashtable res = new Hashtable<>(); + res.put(DirectoryConf.uri.name(), uriStr.toString()); + addIpaConfig(kerberosRealm, res); + return res; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapConnection.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapConnection.java new file mode 100644 index 000000000..efc8cbcf8 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapConnection.java @@ -0,0 +1,162 @@ +package org.argeo.cms.directory.ldap; + +import java.util.Dictionary; +import java.util.Hashtable; + +import javax.naming.CommunicationException; +import javax.naming.Context; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapName; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.cms.transaction.WorkingCopy; + +/** A synchronized wrapper for a single {@link InitialLdapContext}. */ +// TODO implement multiple contexts and connection pooling. +public class LdapConnection { + private InitialLdapContext initialLdapContext = null; + + public LdapConnection(String url, Dictionary properties) { + try { + Hashtable connEnv = new Hashtable(); + connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + connEnv.put(Context.PROVIDER_URL, url); + connEnv.put("java.naming.ldap.attributes.binary", LdapAttr.userPassword.name()); + // use pooling in order to avoid connection timeout +// connEnv.put("com.sun.jndi.ldap.connect.pool", "true"); +// connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000); + + initialLdapContext = new InitialLdapContext(connEnv, null); + // StartTlsResponse tls = (StartTlsResponse) ctx + // .extendedOperation(new StartTlsRequest()); + // tls.negotiate(); + Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION); + if (securityAuthentication != null) + initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication); + else + initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple"); + Object principal = properties.get(Context.SECURITY_PRINCIPAL); + if (principal != null) { + initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString()); + Object creds = properties.get(Context.SECURITY_CREDENTIALS); + if (creds != null) { + initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString()); + } + } + } catch (NamingException e) { + throw new IllegalStateException("Cannot connect to LDAP", e); + } + + } + + public void init() { + + } + + public void destroy() { + try { + // tls.close(); + initialLdapContext.close(); + initialLdapContext = null; + } catch (NamingException e) { + e.printStackTrace(); + } + } + + protected InitialLdapContext getLdapContext() { + return initialLdapContext; + } + + protected void reconnect() throws NamingException { + initialLdapContext.reconnect(initialLdapContext.getConnectControls()); + } + + public synchronized NamingEnumeration search(LdapName searchBase, String searchFilter, + SearchControls searchControls) throws NamingException { + NamingEnumeration results; + try { + results = getLdapContext().search(searchBase, searchFilter, searchControls); + } catch (CommunicationException e) { + reconnect(); + results = getLdapContext().search(searchBase, searchFilter, searchControls); + } + return results; + } + + public synchronized Attributes getAttributes(LdapName name) throws NamingException { + try { + return getLdapContext().getAttributes(name); + } catch (CommunicationException e) { + reconnect(); + return getLdapContext().getAttributes(name); + } + } + + public synchronized boolean entryExists(LdapName name) throws NamingException { + String[] noAttrOID = new String[] { "1.1" }; + try { + getLdapContext().getAttributes(name, noAttrOID); + return true; + } catch (CommunicationException e) { + reconnect(); + getLdapContext().getAttributes(name, noAttrOID); + return true; + } catch (NameNotFoundException e) { + return false; + } + } + + public synchronized void prepareChanges(WorkingCopy wc) throws NamingException { + // make sure connection will work + reconnect(); + + // delete + for (LdapName dn : wc.getDeletedData().keySet()) { + if (!entryExists(dn)) + throw new IllegalStateException("User to delete no found " + dn); + } + // add + for (LdapName dn : wc.getNewData().keySet()) { + if (entryExists(dn)) + throw new IllegalStateException("User to create found " + dn); + } + // modify + for (LdapName dn : wc.getModifiedData().keySet()) { + if (!wc.getNewData().containsKey(dn) && !entryExists(dn)) + throw new IllegalStateException("User to modify not found " + dn); + } + + } + +// protected boolean entryExists(LdapName dn) throws NamingException { +// try { +// return getAttributes(dn).size() != 0; +// } catch (NameNotFoundException e) { +// return false; +// } +// } + + public synchronized void commitChanges(LdapEntryWorkingCopy wc) throws NamingException { + // delete + for (LdapName dn : wc.getDeletedData().keySet()) { + getLdapContext().destroySubcontext(dn); + } + // add + for (LdapName dn : wc.getNewData().keySet()) { + LdapEntry user = wc.getNewData().get(dn); + getLdapContext().createSubcontext(dn, user.getAttributes()); + } + // modify + for (LdapName dn : wc.getModifiedData().keySet()) { + Attributes modifiedAttrs = wc.getModifiedData().get(dn); + getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java new file mode 100644 index 000000000..cdc1c9fe6 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java @@ -0,0 +1,264 @@ +package org.argeo.cms.directory.ldap; + +import static org.argeo.api.acr.ldap.LdapAttr.objectClass; + +import java.util.ArrayList; +import java.util.List; + +import javax.naming.AuthenticationNotSupportedException; +import javax.naming.Binding; +import javax.naming.InvalidNameException; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.ldap.LdapObj; +import org.argeo.api.cms.directory.HierarchyUnit; + +/** A user admin based on a LDAP server. */ +public class LdapDao extends AbstractLdapDirectoryDao { + private LdapConnection ldapConnection; + + public LdapDao(AbstractLdapDirectory directory) { + super(directory); + } + + @Override + public void init() { + ldapConnection = new LdapConnection(getDirectory().getUri().toString(), getDirectory().cloneConfigProperties()); + } + + public void destroy() { + ldapConnection.destroy(); + } + + @Override + public boolean checkConnection() { + try { + return ldapConnection.entryExists(getDirectory().getBaseDn()); + } catch (NamingException e) { + return false; + } + } + + @Override + public boolean entryExists(LdapName dn) { + try { + return ldapConnection.entryExists(dn); + } catch (NameNotFoundException e) { + return false; + } catch (NamingException e) { + throw new IllegalStateException("Cannot check " + dn, e); + } + } + + @Override + public LdapEntry doGetEntry(LdapName name) throws NameNotFoundException { +// if (!entryExists(name)) +// throw new NameNotFoundException(name + " was not found in " + getDirectory().getBaseDn()); + try { + Attributes attrs = ldapConnection.getAttributes(name); + + LdapEntry res; + Rdn technicalRdn = LdapNameUtils.getParentRdn(name); + if (getDirectory().getGroupBaseRdn().equals(technicalRdn)) { + if (attrs.size() == 0) {// exists but not accessible + attrs = new BasicAttributes(); + attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name()); + attrs.put(LdapAttr.objectClass.name(), getDirectory().getGroupObjectClass()); + } + res = newGroup(name); + } else if (getDirectory().getSystemRoleBaseRdn().equals(technicalRdn)) { + if (attrs.size() == 0) {// exists but not accessible + attrs = new BasicAttributes(); + attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name()); + attrs.put(LdapAttr.objectClass.name(), getDirectory().getGroupObjectClass()); + } + res = newGroup(name); + } else if (getDirectory().getUserBaseRdn().equals(technicalRdn)) { + if (attrs.size() == 0) {// exists but not accessible + attrs = new BasicAttributes(); + attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name()); + attrs.put(LdapAttr.objectClass.name(), getDirectory().getUserObjectClass()); + } + res = newUser(name); + } else { + res = new DefaultLdapEntry(getDirectory(), name); + } + return res; + } catch (NameNotFoundException e) { + throw e; + } catch (NamingException e) { + throw new IllegalStateException("Cannot retrieve entry " + name, e); + } + } + + @Override + public Attributes doGetAttributes(LdapName name) { + try { + Attributes attrs = ldapConnection.getAttributes(name); + return attrs; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get attributes for " + name); + } + } + + @Override + public List doGetEntries(LdapName searchBase, String f, boolean deep) { + ArrayList res = new ArrayList<>(); + try { + String searchFilter = f != null ? f.toString() + : "(|(" + objectClass.name() + "=" + getDirectory().getUserObjectClass() + ")(" + objectClass.name() + + "=" + getDirectory().getGroupObjectClass() + "))"; + SearchControls searchControls = new SearchControls(); + // only attribute needed is objectClass + searchControls.setReturningAttributes(new String[] { objectClass.name() }); + // FIXME make one level consistent with deep + searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE); + + // LdapName searchBase = getBaseDn(); + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); + + results: while (results.hasMoreElements()) { + SearchResult searchResult = results.next(); + Attributes attrs = searchResult.getAttributes(); + Attribute objectClassAttr = attrs.get(objectClass.name()); + LdapName dn = toDn(searchBase, searchResult); + LdapEntry role; + if (objectClassAttr.contains(getDirectory().getGroupObjectClass()) + || objectClassAttr.contains(getDirectory().getGroupObjectClass().toLowerCase())) + role = newGroup(dn); + else if (objectClassAttr.contains(getDirectory().getUserObjectClass()) + || objectClassAttr.contains(getDirectory().getUserObjectClass().toLowerCase())) + role = newUser(dn); + else { +// log.warn("Unsupported LDAP type for " + searchResult.getName()); + continue results; + } + res.add(role); + } + return res; + } catch (AuthenticationNotSupportedException e) { + // ignore (typically an unsupported anonymous bind) + // TODO better logging + return res; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get roles for filter " + f, e); + } + } + + private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException { + return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName()); + } + + @Override + public List getDirectGroups(LdapName dn) { + List directGroups = new ArrayList(); + try { + String searchFilter = "(&(" + objectClass + "=" + getDirectory().getGroupObjectClass() + ")(" + + getDirectory().getMemberAttributeId() + "=" + dn + "))"; + + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + LdapName searchBase = getDirectory().getBaseDn(); + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); + + while (results.hasMoreElements()) { + SearchResult searchResult = (SearchResult) results.nextElement(); + directGroups.add(toDn(searchBase, searchResult)); + } + return directGroups; + } catch (NamingException e) { + throw new IllegalStateException("Cannot populate direct members of " + dn, e); + } + } + + @Override + public void prepare(LdapEntryWorkingCopy wc) { + try { + ldapConnection.prepareChanges(wc); + } catch (NamingException e) { + throw new IllegalStateException("Cannot prepare LDAP", e); + } + } + + @Override + public void commit(LdapEntryWorkingCopy wc) { + try { + ldapConnection.commitChanges(wc); + } catch (NamingException e) { + throw new IllegalStateException("Cannot commit LDAP", e); + } + } + + @Override + public void rollback(LdapEntryWorkingCopy wc) { + // prepare not impacting + } + + /* + * HIERARCHY + */ + + @Override + public Iterable doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) { + List res = new ArrayList<>(); + try { + String structuralFilter = functionalOnly ? "" + : "(" + getDirectory().getUserBaseRdn() + ")(" + getDirectory().getGroupBaseRdn() + ")(" + + getDirectory().getSystemRoleBaseRdn() + ")"; + String searchFilter = "(|(" + objectClass + "=" + LdapObj.organizationalUnit.name() + ")(" + objectClass + + "=" + LdapObj.organization.name() + ")" + structuralFilter + ")"; + + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE); + // no attributes needed + searchControls.setReturningAttributes(new String[0]); + + NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); + + while (results.hasMoreElements()) { + SearchResult searchResult = (SearchResult) results.nextElement(); + LdapName dn = toDn(searchBase, searchResult); +// Attributes attrs = searchResult.getAttributes(); + LdapHierarchyUnit hierarchyUnit = new LdapHierarchyUnit(getDirectory(), dn); + if (functionalOnly) { + if (hierarchyUnit.isFunctional()) + res.add(hierarchyUnit); + } else { + res.add(hierarchyUnit); + } + } + return res; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get direct hierarchy units ", e); + } + } + + @Override + public HierarchyUnit doGetHierarchyUnit(LdapName dn) { + try { + if (getDirectory().getBaseDn().equals(dn)) + return getDirectory(); + if (!dn.startsWith(getDirectory().getBaseDn())) + throw new IllegalArgumentException(dn + " does not start with base DN " + getDirectory().getBaseDn()); + if (!ldapConnection.entryExists(dn)) + return null; + return new LdapHierarchyUnit(getDirectory(), dn); + } catch (NameNotFoundException e) { + return null; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get hierarchy unit " + dn, e); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDirectoryDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDirectoryDao.java new file mode 100644 index 000000000..03b03ea11 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDirectoryDao.java @@ -0,0 +1,37 @@ +package org.argeo.cms.directory.ldap; + +import java.util.List; + +import javax.naming.NameNotFoundException; +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.transaction.WorkingCopyProcessor; + +/** Low-level access to an LDAP/LDIF directory. */ +public interface LdapDirectoryDao extends WorkingCopyProcessor { + boolean checkConnection(); + + boolean entryExists(LdapName dn); + + LdapEntry doGetEntry(LdapName name) throws NameNotFoundException; + + Attributes doGetAttributes(LdapName name); + + List doGetEntries(LdapName searchBase, String filter, boolean deep); + + List getDirectGroups(LdapName dn); + + Iterable doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly); + + HierarchyUnit doGetHierarchyUnit(LdapName dn); + + LdapEntry newUser(LdapName name); + + LdapEntry newGroup(LdapName name); + + void init(); + + void destroy(); +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java new file mode 100644 index 000000000..fa95c9615 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java @@ -0,0 +1,65 @@ +package org.argeo.cms.directory.ldap; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Dictionary; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeSet; + +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; + +import org.argeo.api.acr.ldap.LdapAttr; + +/** An LDAP entry. */ +public interface LdapEntry { + LdapName getDn(); + + Attributes getAttributes(); + + void publishAttributes(Attributes modifiedAttributes); + + List getReferences(String attributeId); + + Dictionary getProperties(); + + boolean hasCredential(String key, Object value); + + /* + * UTILITIES + */ + /** + * Convert a collection of object classes to the format expected by an LDAP + * backend. + */ + public static void addObjectClasses(Dictionary properties, Collection objectClasses) { + String value = properties.get(LdapAttr.objectClasses.name()).toString(); + Set currentObjectClasses = new TreeSet<>(Arrays.asList(value.toString().split("\n"))); + currentObjectClasses.addAll(objectClasses); + StringJoiner values = new StringJoiner("\n"); + currentObjectClasses.forEach((s) -> values.add(s)); + properties.put(LdapAttr.objectClasses.name(), values.toString()); + } + + public static Object getLocalized(Dictionary properties, String key, Locale locale) { + if (locale == null) + return null; + Object value = null; + value = properties.get(key + ";lang-" + locale.getLanguage() + "-" + locale.getCountry()); + if (value == null) + value = properties.get(key + ";lang-" + locale.getLanguage()); + return value; + } + + public static String toLocalizedKey(String key, Locale locale) { + String country = locale.getCountry(); + if ("".equals(country)) { + return key + ";lang-" + locale.getLanguage(); + } else { + return key + ";lang-" + locale.getLanguage() + "-" + locale.getCountry(); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntryWorkingCopy.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntryWorkingCopy.java new file mode 100644 index 000000000..58e565a37 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntryWorkingCopy.java @@ -0,0 +1,19 @@ +package org.argeo.cms.directory.ldap; + +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.transaction.AbstractWorkingCopy; + +/** Working copy for a user directory being edited. */ +public class LdapEntryWorkingCopy extends AbstractWorkingCopy { + @Override + protected LdapName getId(LdapEntry entry) { + return (LdapName) entry.getDn().clone(); + } + + @Override + protected Attributes cloneAttributes(LdapEntry entry) { + return (Attributes) entry.getAttributes().clone(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java new file mode 100644 index 000000000..b60ee0c68 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java @@ -0,0 +1,85 @@ +package org.argeo.cms.directory.ldap; + +import java.util.Locale; + +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.argeo.api.cms.directory.HierarchyUnit; + +/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */ +public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit { +// private final boolean functional; + + private final Type type; + + public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn) { + super(directory, dn); + + Rdn rdn = LdapNameUtils.getLastRdn(dn); + if (directory.getUserBaseRdn().equals(rdn)) + type = Type.PEOPLE; + else if (directory.getGroupBaseRdn().equals(rdn)) + type = Type.GROUPS; + else if (directory.getSystemRoleBaseRdn().equals(rdn)) + type = Type.ROLES; + else + type = Type.FUNCTIONAL; +// functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn) +// || directory.getSystemRoleBaseRdn().equals(rdn)); + } + + @Override + public HierarchyUnit getParent() { + return getDirectoryDao().doGetHierarchyUnit(LdapNameUtils.getParent(getDn())); + } + + @Override + public Iterable getDirectHierarchyUnits(boolean functionalOnly) { + return getDirectoryDao().doGetDirectHierarchyUnits(getDn(), functionalOnly); + } + + @Override + public HierarchyUnit getDirectChild(Type type) { + return switch (type) { + case ROLES -> + getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getSystemRoleBaseRdn())); + case PEOPLE -> getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getUserBaseRdn())); + case GROUPS -> getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getGroupBaseRdn())); + case FUNCTIONAL -> throw new IllegalArgumentException("Type must be a technical type"); + }; + } + + @Override + public boolean isType(Type type) { + return this.type.equals(type); + } + + @Override + public String getHierarchyUnitName() { + String name = LdapNameUtils.getLastRdnValue(getDn()); + // TODO check ou, o, etc. + return name; + } + + @Override + public String getHierarchyUnitLabel(Locale locale) { + String key = LdapNameUtils.getLastRdn(getDn()).getType(); + Object value = LdapEntry.getLocalized(getProperties(), key, locale); + if (value == null) + value = getHierarchyUnitName(); + assert value != null; + return value.toString(); + } + + @Override + public String getBase() { + return getDn().toString(); + } + + @Override + public String toString() { + return "Hierarchy Unit " + getDn().toString(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java new file mode 100644 index 000000000..74f23da67 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java @@ -0,0 +1,69 @@ +package org.argeo.cms.directory.ldap; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +/** Utilities to simplify using {@link LdapName}. */ +public class LdapNameUtils { + + public static LdapName relativeName(LdapName prefix, LdapName dn) { + try { + if (!dn.startsWith(prefix)) + throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn); + LdapName res = (LdapName) dn.clone(); + for (int i = 0; i < prefix.size(); i++) { + res.remove(0); + } + return res; + } catch (InvalidNameException e) { + throw new IllegalStateException("Cannot find realtive name", e); + } + } + + public static LdapName getParent(LdapName dn) { + try { + LdapName parent = (LdapName) dn.clone(); + parent.remove(parent.size() - 1); + return parent; + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Cannot get parent of " + dn, e); + } + } + + public static Rdn getParentRdn(LdapName dn) { + if (dn.size() < 2) + throw new IllegalArgumentException(dn + " has no parent"); + Rdn parentRdn = dn.getRdn(dn.size() - 2); + return parentRdn; + } + + public static LdapName toLdapName(String distinguishedName) { + try { + return new LdapName(distinguishedName); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e); + } + } + + public static Rdn getLastRdn(LdapName dn) { + return dn.getRdn(dn.size() - 1); + } + + public static String getLastRdnAsString(LdapName dn) { + return getLastRdn(dn).toString(); + } + + public static String getLastRdnValue(String dn) { + return getLastRdnValue(toLdapName(dn)); + } + + public static String getLastRdnValue(LdapName dn) { + return getLastRdn(dn).getValue().toString(); + } + + /** singleton */ + private LdapNameUtils() { + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java new file mode 100644 index 000000000..52148dfab --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java @@ -0,0 +1,306 @@ +package org.argeo.cms.directory.ldap; + +import static org.argeo.api.acr.ldap.LdapAttr.objectClass; +import static org.argeo.api.acr.ldap.LdapObj.inetOrgPerson; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; + +import org.argeo.api.acr.ldap.LdapObj; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; + +/** A user admin based on a LDIF files. */ +public class LdifDao extends AbstractLdapDirectoryDao { + private NavigableMap entries = new TreeMap<>(); + private NavigableMap hierarchy = new TreeMap<>(); + + private NavigableMap values = new TreeMap<>(); + + public LdifDao(AbstractLdapDirectory directory) { + super(directory); + } + + public void init() { + String uri = getDirectory().getUri(); + if (uri == null) + return; + try { + URI u = new URI(uri); + if (u.getScheme().equals("file")) { + File file = new File(u); + if (!file.exists()) + return; + } + load(u.toURL().openStream()); + } catch (IOException | URISyntaxException e) { + throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e); + } + } + + public void save() { + if (getDirectory().getUri() == null) + return; // ignore + if (getDirectory().isReadOnly()) + throw new IllegalStateException( + "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only"); + try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) { + save(out); + } catch (IOException | URISyntaxException e) { + throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e); + } + } + + public void save(OutputStream out) throws IOException { + try { + LdifWriter ldifWriter = new LdifWriter(out); + for (LdapName name : hierarchy.keySet()) + ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes()); + for (LdapName name : entries.keySet()) + ldifWriter.writeEntry(name, entries.get(name).getAttributes()); + } finally { + out.close(); + } + } + + public void load(InputStream in) { + try { + entries.clear(); + hierarchy.clear(); + + LdifParser ldifParser = new LdifParser(); + SortedMap allEntries = ldifParser.read(in); + for (LdapName key : allEntries.keySet()) { + Attributes attributes = allEntries.get(key); + // check for inconsistency + Set lowerCase = new HashSet(); + NamingEnumeration ids = attributes.getIDs(); + while (ids.hasMoreElements()) { + String id = ids.nextElement().toLowerCase(); + if (lowerCase.contains(id)) + throw new IllegalStateException(key + " has duplicate id " + id); + lowerCase.add(id); + } + + values.put(key, attributes); + + // analyse object classes + NamingEnumeration objectClasses = attributes.get(objectClass.name()).getAll(); + // System.out.println(key); + objectClasses: while (objectClasses.hasMore()) { + String objectClass = objectClasses.next().toString(); + // System.out.println(" " + objectClass); + if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) { + entries.put(key, newUser(key)); + break objectClasses; + } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) { + entries.put(key, newGroup(key)); + break objectClasses; + } else if (objectClass.equalsIgnoreCase(LdapObj.organizationalUnit.name())) { + // TODO skip if it does not contain groups or users + hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key)); + break objectClasses; + } + } + } + + } catch (NamingException | IOException e) { + throw new IllegalStateException("Cannot load user admin service from LDIF", e); + } + } + + public void destroy() { +// if (users == null || groups == null) + if (entries == null) + throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed"); +// users = null; +// groups = null; + entries = null; + } + + /* + * USER ADMIN + */ + + @Override + public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException { + if (entries.containsKey(key)) + return entries.get(key); + throw new NameNotFoundException(key + " not persisted"); + } + + @Override + public Attributes doGetAttributes(LdapName name) { + if (!values.containsKey(name)) + throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn()); + return values.get(name); + } + + @Override + public boolean checkConnection() { + return true; + } + + @Override + public boolean entryExists(LdapName dn) { + return entries.containsKey(dn);// || groups.containsKey(dn); + } + + @Override + public List doGetEntries(LdapName searchBase, String f, boolean deep) { + Objects.requireNonNull(searchBase); + ArrayList res = new ArrayList<>(); + if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) { + res.addAll(entries.values()); + } else { + filterRoles(entries, searchBase, f, deep, res); + } + return res; + } + + private void filterRoles(SortedMap map, LdapName searchBase, String f, boolean deep, + List res) { + // FIXME get rid of OSGi references + try { + // TODO reduce map with search base ? + Filter filter = f != null ? FrameworkUtil.createFilter(f) : null; + roles: for (LdapEntry user : map.values()) { + LdapName dn = user.getDn(); + if (dn.startsWith(searchBase)) { + if (!deep && dn.size() != (searchBase.size() + 1)) + continue roles; + if (filter == null) + res.add(user); + else { + if (user instanceof Role) { + if (filter.match(((Role) user).getProperties())) + res.add(user); + } + } + } + } + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Cannot create filter " + f, e); + } + + } + + @Override + public List getDirectGroups(LdapName dn) { + List directGroups = new ArrayList(); + entries: for (LdapName name : entries.keySet()) { + LdapEntry group; + try { + LdapEntry entry = doGetEntry(name); + if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) { + group = entry; + } else { + continue entries; + } + } catch (NameNotFoundException e) { + throw new IllegalArgumentException("Group " + dn + " not found", e); + } + if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) { + directGroups.add(group.getDn()); + } + } + return directGroups; + } + + @Override + public void prepare(LdapEntryWorkingCopy wc) { + // delete + for (LdapName dn : wc.getDeletedData().keySet()) { + if (entries.containsKey(dn)) + entries.remove(dn); + else + throw new IllegalStateException("User to delete not found " + dn); + } + // add + for (LdapName dn : wc.getNewData().keySet()) { + LdapEntry user = (LdapEntry) wc.getNewData().get(dn); + if (entries.containsKey(dn)) + throw new IllegalStateException("User to create found " + dn); + entries.put(dn, user); + } + // modify + for (LdapName dn : wc.getModifiedData().keySet()) { + Attributes modifiedAttrs = wc.getModifiedData().get(dn); + LdapEntry user; + try { + user = doGetEntry(dn); + } catch (NameNotFoundException e) { + throw new IllegalStateException("User to modify no found " + dn, e); + } + if (user == null) + throw new IllegalStateException("User to modify no found " + dn); + user.publishAttributes(modifiedAttrs); + } + } + + @Override + public void commit(LdapEntryWorkingCopy wc) { + save(); + } + + @Override + public void rollback(LdapEntryWorkingCopy wc) { + init(); + } + + /* + * HIERARCHY + */ + @Override + public HierarchyUnit doGetHierarchyUnit(LdapName dn) { + if (getDirectory().getBaseDn().equals(dn)) + return getDirectory(); + return hierarchy.get(dn); + } + + @Override + public Iterable doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) { + List res = new ArrayList<>(); + for (LdapName n : hierarchy.keySet()) { + if (n.size() == searchBase.size() + 1) { + if (n.startsWith(searchBase)) { + HierarchyUnit hu = hierarchy.get(n); + if (functionalOnly) { + if (hu.isFunctional()) + res.add(hu); + } else { + res.add(hu); + } + } + } + } + return res; + } + + public void scope(LdifDao scoped) { + scoped.entries = Collections.unmodifiableNavigableMap(entries); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifParser.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifParser.java new file mode 100644 index 000000000..d0e6b76d5 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifParser.java @@ -0,0 +1,161 @@ +package org.argeo.cms.directory.ldap; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.InvalidNameException; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.argeo.api.acr.ldap.LdapAttr; + +/** Basic LDIF parser. */ +public class LdifParser { + private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + protected Attributes addAttributes(SortedMap res, int lineNumber, LdapName currentDn, + Attributes currentAttributes) { + try { + Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1); + Attribute nameAttr = currentAttributes.get(nameRdn.getType()); + if (nameAttr == null) + currentAttributes.put(nameRdn.getType(), nameRdn.getValue()); + else if (!nameAttr.get().equals(nameRdn.getValue())) + throw new IllegalStateException( + "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn + + " (shortly before line " + lineNumber + " in LDIF file)"); + Attributes previous = res.put(currentDn, currentAttributes); + return previous; + } catch (NamingException e) { + throw new IllegalStateException("Cannot add " + currentDn, e); + } + } + + /** With UTF-8 charset */ + public SortedMap 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 read(Reader reader) throws IOException { + SortedMap res = new TreeMap(); + try { + List lines = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(reader)) { + String line; + while ((line = br.readLine()) != null) { + lines.add(line); + } + } + if (lines.size() == 0) + return res; + // add an empty new line since the last line is not checked + if (!lines.get(lines.size() - 1).equals("")) + lines.add(""); + + LdapName currentDn = null; + Attributes currentAttributes = null; + StringBuilder currentEntry = new StringBuilder(); + + readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + String line = lines.get(lineNumber); + boolean isLastLine = false; + if (lineNumber == lines.size() - 1) + isLastLine = true; + if (line.startsWith(" ")) { + currentEntry.append(line.substring(1)); + if (!isLastLine) + continue readLines; + } + + if (currentEntry.length() != 0 || isLastLine) { + // read previous attribute + StringBuilder attrId = new StringBuilder(8); + boolean isBase64 = false; + readAttrId: for (int i = 0; i < currentEntry.length(); i++) { + char c = currentEntry.charAt(i); + if (c == ':') { + if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':') + isBase64 = true; + currentEntry.delete(0, i + (isBase64 ? 2 : 1)); + break readAttrId; + } else { + attrId.append(c); + } + } + + String attributeId = attrId.toString(); + // TODO should we really trim the end of the string as well? + String cleanValueStr = currentEntry.toString().trim(); + Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr; + + // manage DN attributes + if (attributeId.equals(LdapAttr.DN) || isLastLine) { + if (currentDn != null) { + // + // ADD + // + Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes); + if (previous != null) { +// log.warn("There was already an entry with DN " + currentDn +// + ", which has been discarded by a subsequent one."); + } + } + + if (attributeId.equals(LdapAttr.DN)) + try { + currentDn = new LdapName(attributeValue.toString()); + currentAttributes = new BasicAttributes(true); + } catch (InvalidNameException e) { +// log.error(attributeValue + " not a valid DN, skipping the entry."); + currentDn = null; + currentAttributes = null; + } + } + + // store attribute + if (currentAttributes != null) { + Attribute attribute = currentAttributes.get(attributeId); + if (attribute == null) { + attribute = new BasicAttribute(attributeId); + currentAttributes.put(attribute); + } + attribute.add(attributeValue); + } + currentEntry = new StringBuilder(); + } + currentEntry.append(line); + } + } finally { + try { + reader.close(); + } catch (IOException e) { + // silent + } + } + return res; + } +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifWriter.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifWriter.java new file mode 100644 index 000000000..69a867204 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifWriter.java @@ -0,0 +1,104 @@ +package org.argeo.cms.directory.ldap; + +import static org.argeo.api.acr.ldap.LdapAttr.DN; +import static org.argeo.api.acr.ldap.LdapAttr.member; +import static org.argeo.api.acr.ldap.LdapAttr.objectClass; +import static org.argeo.api.acr.ldap.LdapAttr.uniqueMember; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +/** Basic LDIF writer */ +public class LdifWriter { + private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private final Writer writer; + + /** Writer must be closed by caller */ + public LdifWriter(Writer writer) { + this.writer = writer; + } + + /** Stream must be closed by caller */ + public LdifWriter(OutputStream out) { + this(new OutputStreamWriter(out, DEFAULT_CHARSET)); + } + + public void writeEntry(LdapName name, Attributes attributes) throws IOException { + try { + // check consistency + Rdn nameRdn = name.getRdn(name.size() - 1); + Attribute nameAttr = attributes.get(nameRdn.getType()); + if (!nameAttr.get().equals(nameRdn.getValue())) + throw new IllegalArgumentException( + "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name); + + writer.append(DN + ": ").append(name.toString()).append('\n'); + Attribute objectClassAttr = attributes.get(objectClass.name()); + if (objectClassAttr != null) + writeAttribute(objectClassAttr); + attributes: for (NamingEnumeration 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 attrs = attributes.getAll(); attrs.hasMore();) { + Attribute attribute = attrs.next(); + if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name())) + writeMemberAttribute(attribute); + } + writer.append('\n'); + writer.flush(); + } catch (NamingException e) { + throw new IllegalStateException("Cannot write LDIF", e); + } + } + + public void write(Map 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 values = new TreeSet<>(); + for (NamingEnumeration attrValues = attribute.getAll(); attrValues.hasMore();) { + String value = attrValues.next().toString(); + values.add(value); + } + + for (String value : values) { + writer.append(attribute.getID()).append(": ").append(value).append('\n'); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/SharedSecret.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/SharedSecret.java new file mode 100644 index 000000000..2c52ee12a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/directory/ldap/SharedSecret.java @@ -0,0 +1,48 @@ +package org.argeo.cms.directory.ldap; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.argeo.api.acr.ldap.NamingUtils; + +public class SharedSecret extends AuthPassword { + public final static String X_SHARED_SECRET = "X-SharedSecret"; + private final Instant expiry; + + public SharedSecret(String authInfo, String authValue) { + super(authInfo, authValue); + expiry = null; + } + + public SharedSecret(AuthPassword authPassword) { + super(authPassword); + String authInfo = getAuthInfo(); + if (authInfo.length() == 16) { + expiry = NamingUtils.ldapDateToInstant(authInfo); + } else { + expiry = null; + } + } + + public SharedSecret(ZonedDateTime expiryTimestamp, String value) { + super(NamingUtils.instantToLdapDate(expiryTimestamp), value); + expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant(); + } + + public SharedSecret(int hours, String value) { + this(ZonedDateTime.now().plusHours(hours), value); + } + + @Override + protected String getExpectedAuthScheme() { + return X_SHARED_SECRET; + } + + public boolean isExpired() { + if (expiry == null) + return false; + return expiry.isBefore(Instant.now()); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/dns/DnsBrowser.java b/org.argeo.cms/src/org/argeo/cms/dns/DnsBrowser.java new file mode 100644 index 000000000..c6b653015 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dns/DnsBrowser.java @@ -0,0 +1,216 @@ +package org.argeo.cms.dns; + +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedSet; +import java.util.StringJoiner; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +public class DnsBrowser implements Closeable { + private final DirContext initialCtx; + + public DnsBrowser() { + this(new ArrayList<>()); + } + + public DnsBrowser(List dnsServerUrls) { + try { + Objects.requireNonNull(dnsServerUrls); + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); + if (!dnsServerUrls.isEmpty()) { + boolean specified = false; + StringJoiner providerUrl = new StringJoiner(" "); + for (String dnsUrl : dnsServerUrls) { + if (dnsUrl != null) { + providerUrl.add(dnsUrl); + specified = true; + } + } + if (specified) + env.put(Context.PROVIDER_URL, providerUrl.toString()); + } + initialCtx = new InitialDirContext(env); + } catch (NamingException e) { + throw new IllegalStateException("Cannot initialise DNS borowser.", e); + } + } + + public Map> getAllRecords(String name) { + try { + Map> res = new TreeMap<>(); + Attributes attrs = initialCtx.getAttributes(name); + NamingEnumeration ids = attrs.getIDs(); + while (ids.hasMore()) { + String recordType = ids.next(); + List lst = new ArrayList(); + res.put(recordType, lst); + Attribute attr = attrs.get(recordType); + addValues(attr, lst); + } + return Collections.unmodifiableMap(res); + } catch (NamingException e) { + throw new IllegalStateException("Cannot get allrecords of " + name, e); + } + } + + /** + * Return a single record (typically A, AAAA, etc. or null if not available. + * Will fail if multiple records. + */ + public String getRecord(String name, String recordType) { + try { + Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType }); + if (attrs.size() == 0) + return null; + Attribute attr = attrs.get(recordType); + if (attr.size() > 1) + throw new IllegalArgumentException("Multiple record type " + recordType); + assert attr.size() != 0; + Object value = attr.get(); + assert value != null; + return value.toString(); + } catch (NameNotFoundException e) { + return null; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get DNS entry " + recordType + " of " + name, e); + } + } + + /** + * Return records of a given type. + */ + public List getRecords(String name, String recordType) { + try { + List res = new ArrayList(); + Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType }); + Attribute attr = attrs.get(recordType); + addValues(attr, res); + return res; + } catch (NamingException e) { + throw new IllegalStateException("Cannot get records " + recordType + " of " + name, e); + } + } + + /** Ordered, with preferred first. */ + public List getSrvRecordsAsHosts(String name, boolean withPort) { + List raw = getRecords(name, "SRV"); + if (raw.size() == 0) + return null; + SortedSet 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 lst = new ArrayList<>(); + for (SrvRecord order : res) { + lst.add(order.toHost(withPort)); + } + return Collections.unmodifiableList(lst); + } + + private void addValues(Attribute attr, List 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 listEntries(String name) { + try { + List res = new ArrayList(); + NamingEnumeration ne = initialCtx.listBindings(name); + while (ne.hasMore()) { + Binding b = ne.next(); + res.add(b.getName()); + } + return Collections.unmodifiableList(res); + } catch (NamingException e) { + throw new IllegalStateException("Cannot list entries of " + name, e); + } + } + + @Override + public void close() throws IOException { + destroy(); + } + + public void destroy() { + try { + initialCtx.close(); + } catch (NamingException e) { + // silent + } + } + + public static void main(String[] args) { + if (args.length == 0) { + printUsage(System.err); + System.exit(1); + } + try (DnsBrowser dnsBrowser = new DnsBrowser()) { + String hostname = args[0]; + String recordType = args.length > 1 ? args[1] : "A"; + if (recordType.equals("*")) { + Map> 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 [ | *]"); + } + +} \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/dns/SrvRecord.java b/org.argeo.cms/src/org/argeo/cms/dns/SrvRecord.java new file mode 100644 index 000000000..bdbdc769a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/dns/SrvRecord.java @@ -0,0 +1,52 @@ +package org.argeo.cms.dns; + +class SrvRecord implements Comparable { + private final Integer priority; + private final Integer weight; + private final Integer port; + private final String hostname; + + public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) { + this.priority = priority; + this.weight = weight; + this.port = port; + this.hostname = hostname; + } + + @Override + public int compareTo(SrvRecord other) { + // https: // en.wikipedia.org/wiki/SRV_record + if (priority != other.priority) + return priority - other.priority; + if (weight != other.weight) + return other.weight - other.weight; + String host = toHost(false); + String otherHost = other.toHost(false); + if (host.length() == otherHost.length()) + return host.compareTo(otherHost); + else + return host.length() - otherHost.length(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SrvRecord) { + SrvRecord other = (SrvRecord) obj; + return priority == other.priority && weight == other.weight && port == other.port + && hostname.equals(other.hostname); + } + return false; + } + + @Override + public String toString() { + return priority + " " + weight; + } + + public String toHost(boolean withPort) { + String hostStr = hostname; + if (hostname.charAt(hostname.length() - 1) == '.') + hostStr = hostname.substring(0, hostname.length() - 1); + return hostStr + (withPort ? ":" + port : ""); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/BasicSyncFileVisitor.java b/org.argeo.cms/src/org/argeo/cms/file/BasicSyncFileVisitor.java new file mode 100644 index 000000000..ac81329a9 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/BasicSyncFileVisitor.java @@ -0,0 +1,162 @@ +package org.argeo.cms.file; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; + +/** Synchronises two directory structures. */ +public class BasicSyncFileVisitor extends SimpleFileVisitor { + // 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 syncResult = new SyncResult<>(); + + public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) { + this.sourceBasePath = sourceBasePath; + this.targetBasePath = targetBasePath; + this.delete = delete; + this.recursive = recursive; + } + + @Override + public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException { + if (!recursive && !sourceDir.equals(sourceBasePath)) + return FileVisitResult.SKIP_SUBTREE; + Path targetDir = toTargetPath(sourceDir); + Files.createDirectories(targetDir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException { + if (delete) { + Path targetDir = toTargetPath(sourceDir); + for (Path targetPath : Files.newDirectoryStream(targetDir)) { + Path sourcePath = sourceDir.resolve(targetPath.getFileName()); + if (!Files.exists(sourcePath)) { + try { + FsSyncUtils.delete(targetPath); + deleted(targetPath); + } catch (Exception e) { + deleteFailed(targetPath, exc); + } + } + } + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException { + Path targetFile = toTargetPath(sourceFile); + try { + if (!Files.exists(targetFile)) { + Files.copy(sourceFile, targetFile); + added(sourceFile, targetFile); + } else { + if (shouldOverwrite(sourceFile, targetFile)) { + Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (Exception e) { + copyFailed(sourceFile, targetFile, e); + } + return FileVisitResult.CONTINUE; + } + + protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException { + long sourceSize = Files.size(sourceFile); + long targetSize = Files.size(targetFile); + if (sourceSize != targetSize) { + return true; + } + FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile); + FileTime targetLastModif = Files.getLastModifiedTime(targetFile); + if (sourceLastModif.compareTo(targetLastModif) > 0) + return true; + return shouldOverwriteLaterSameSize(sourceFile, targetFile); + } + + protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) { + return false; + } + +// @Override +// public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException { +// error("Cannot sync " + sourceFile, exc); +// return FileVisitResult.CONTINUE; +// } + + private Path toTargetPath(Path sourcePath) { + Path relativePath = sourceBasePath.relativize(sourcePath); + Path targetPath = targetBasePath.resolve(relativePath.toString()); + return targetPath; + } + + public Path getSourceBasePath() { + return sourceBasePath; + } + + public Path getTargetBasePath() { + return targetBasePath; + } + + protected void added(Path sourcePath, Path targetPath) { + syncResult.getAdded().add(targetPath); + if (isTraceEnabled()) + trace("Added " + sourcePath + " as " + targetPath); + } + + protected void modified(Path sourcePath, Path targetPath) { + syncResult.getModified().add(targetPath); + if (isTraceEnabled()) + trace("Overwritten from " + sourcePath + " to " + targetPath); + } + + protected void copyFailed(Path sourcePath, Path targetPath, Exception e) { + syncResult.addError(sourcePath, targetPath, e); + if (isTraceEnabled()) + error("Cannot copy " + sourcePath + " to " + targetPath, e); + } + + protected void deleted(Path targetPath) { + syncResult.getDeleted().add(targetPath); + if (isTraceEnabled()) + trace("Deleted " + targetPath); + } + + protected void deleteFailed(Path targetPath, Exception e) { + syncResult.addError(null, targetPath, e); + if (isTraceEnabled()) + error("Cannot delete " + targetPath, e); + } + + /** Log error. */ + protected void error(Object obj, Throwable e) { + System.err.println(obj); + e.printStackTrace(); + } + + protected boolean isTraceEnabled() { + return trace; + } + + protected void trace(Object obj) { + System.out.println(obj); + } + + public SyncResult getSyncResult() { + return syncResult; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/ChecksumFactory.java b/org.argeo.cms/src/org/argeo/cms/file/ChecksumFactory.java new file mode 100644 index 000000000..6aea8bea0 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/ChecksumFactory.java @@ -0,0 +1,149 @@ +package org.argeo.cms.file; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.zip.Checksum; + +/** Allows to fine tune how files are read. */ +public class ChecksumFactory { + private int regionSize = 10 * 1024 * 1024; + + public byte[] digest(Path path, final String algo) { + try { + final MessageDigest md = MessageDigest.getInstance(algo); + if (Files.isDirectory(path)) { + long begin = System.currentTimeMillis(); + Files.walkFileTree(path, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (!Files.isDirectory(file)) { + byte[] digest = digest(file, algo); + md.update(digest); + } + return FileVisitResult.CONTINUE; + } + + }); + byte[] digest = md.digest(); + long duration = System.currentTimeMillis() - begin; + System.out.println(printBase64Binary(digest) + " " + path + " (" + duration / 1000 + "s)"); + return digest; + } else { + long begin = System.nanoTime(); + long length = -1; + try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) { + length = channel.size(); + long cursor = 0; + while (cursor < length) { + long effectiveSize = Math.min(regionSize, length - cursor); + MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize); + // md.update(mb); + byte[] buffer = new byte[1024]; + while (mb.hasRemaining()) { + mb.get(buffer); + md.update(buffer); + } + + // sub digest + // mb.flip(); + // MessageDigest subMd = + // MessageDigest.getInstance(algo); + // subMd.update(mb); + // byte[] subDigest = subMd.digest(); + // System.out.println(" -> " + cursor); + // System.out.println(IOUtils.encodeHexString(subDigest)); + // System.out.println(new BigInteger(1, + // subDigest).toString(16)); + // System.out.println(new BigInteger(1, subDigest) + // .toString(Character.MAX_RADIX)); + // System.out.println(printBase64Binary(subDigest)); + + cursor = cursor + regionSize; + } + byte[] digest = md.digest(); + long duration = System.nanoTime() - begin; + System.out.println(printBase64Binary(digest) + " " + path.getFileName() + " (" + duration / 1000000 + + "ms, " + (length / 1024) + "kB, " + (length / (duration / 1000000)) * 1000 / (1024 * 1024) + + " MB/s)"); + return digest; + } + } + } catch (NoSuchAlgorithmException | IOException e) { + throw new IllegalStateException("Cannot digest " + path, e); + } + } + + /** Whether the file should be mapped. */ + protected boolean mapFile(FileChannel fileChannel) throws IOException { + long size = fileChannel.size(); + if (size > (regionSize / 10)) + return true; + return false; + } + + public long checksum(Path path, Checksum crc) { + final int bufferSize = 2 * 1024 * 1024; + long begin = System.currentTimeMillis(); + try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) { + byte[] bytes = new byte[bufferSize]; + long length = channel.size(); + long cursor = 0; + while (cursor < length) { + long effectiveSize = Math.min(regionSize, length - cursor); + MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize); + int nGet; + while (mb.hasRemaining()) { + nGet = Math.min(mb.remaining(), bufferSize); + mb.get(bytes, 0, nGet); + crc.update(bytes, 0, nGet); + } + cursor = cursor + regionSize; + } + return crc.getValue(); + } catch (IOException e) { + throw new IllegalStateException("Cannot checksum " + path, e); + } finally { + long duration = System.currentTimeMillis() - begin; + System.out.println(duration / 1000 + "s"); + } + } + + public static void main(String... args) { + ChecksumFactory cf = new ChecksumFactory(); + // Path path = + // Paths.get("/home/mbaudier/apache-maven-3.2.3-bin.tar.gz"); + Path path; + if (args.length > 0) { + path = Paths.get(args[0]); + } else { + path = Paths.get("/home/mbaudier/Downloads/torrents/CentOS-7-x86_64-DVD-1503-01/" + + "CentOS-7-x86_64-DVD-1503-01.iso"); + } + // long adler = cf.checksum(path, new Adler32()); + // System.out.format("Adler=%d%n", adler); + // long crc = cf.checksum(path, new CRC32()); + // System.out.format("CRC=%d%n", crc); + String algo = "SHA1"; + byte[] digest = cf.digest(path, algo); + System.out.println(algo + " " + printBase64Binary(digest)); + System.out.println(algo + " " + new BigInteger(1, digest).toString(16)); + // String sha1 = printBase64Binary(cf.digest(path, "SHA1")); + // System.out.format("SHA1=%s%n", sha1); + } + + private static String printBase64Binary(byte[] arr) { + return Base64.getEncoder().encodeToString(arr); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/FsSyncUtils.java b/org.argeo.cms/src/org/argeo/cms/file/FsSyncUtils.java new file mode 100644 index 000000000..68eb5abdc --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/FsSyncUtils.java @@ -0,0 +1,62 @@ +package org.argeo.cms.file; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class FsSyncUtils { + /** Sync a source path with a target path. */ + public static void sync(Path sourceBasePath, Path targetBasePath) { + sync(sourceBasePath, targetBasePath, false); + } + + /** Sync a source path with a target path. */ + public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) { + sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true)); + } + + public static void sync(BasicSyncFileVisitor syncFileVisitor) { + try { + Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor); + } catch (Exception e) { + throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with " + + syncFileVisitor.getTargetBasePath(), e); + } + } + + /** + * Deletes this path, recursively if needed. Does nothing if the path does not + * exist. + */ + public static void delete(Path path) { + try { + if (!Files.exists(path)) + return; + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException { + if (e != null) + throw e; + Files.delete(directory); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new RuntimeException("Cannot delete " + path, e); + } + } + + /** Singleton. */ + private FsSyncUtils() { + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/PathSync.java b/org.argeo.cms/src/org/argeo/cms/file/PathSync.java new file mode 100644 index 000000000..dc26aa28b --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/PathSync.java @@ -0,0 +1,59 @@ +package org.argeo.cms.file; + +import java.net.URI; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.spi.FileSystemProvider; +import java.util.concurrent.Callable; + +/** Synchronises two paths. */ +public class PathSync implements Callable> { + 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 call() { + try { + Path sourceBasePath = createPath(sourceUri); + Path targetBasePath = createPath(targetUri); + SyncFileVisitor syncFileVisitor = new SyncFileVisitor(sourceBasePath, targetBasePath, delete, recursive); + Files.walkFileTree(sourceBasePath, syncFileVisitor); + return syncFileVisitor.getSyncResult(); + } catch (Exception e) { + throw new IllegalStateException("Cannot sync " + sourceUri + " to " + targetUri, e); + } + } + + private Path createPath(URI uri) { + Path path; + if (uri.getScheme() == null) { + path = Paths.get(uri.getPath()); + } else if (uri.getScheme().equals("file")) { + FileSystemProvider fsProvider = FileSystems.getDefault().provider(); + path = fsProvider.getPath(uri); + } else if (uri.getScheme().equals("davex")) { + throw new UnsupportedOperationException(); +// FileSystemProvider fsProvider = new DavexFsProvider(); +// path = fsProvider.getPath(uri); +// } else if (uri.getScheme().equals("sftp")) { +// Sftp sftp = new Sftp(uri); +// path = sftp.getBasePath(); + } else + throw new IllegalArgumentException("URI scheme not supported for " + uri); + return path; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/SyncFileVisitor.java b/org.argeo.cms/src/org/argeo/cms/file/SyncFileVisitor.java new file mode 100644 index 000000000..a69d2bf5e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/SyncFileVisitor.java @@ -0,0 +1,30 @@ +package org.argeo.cms.file; + +import java.nio.file.Path; +import java.util.Objects; + +import org.argeo.api.cms.CmsLog; + +/** Synchronises two directory structures. */ +public class SyncFileVisitor extends BasicSyncFileVisitor { + private final static CmsLog log = CmsLog.getLog(SyncFileVisitor.class); + + public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) { + super(sourceBasePath, targetBasePath, delete, recursive); + } + + @Override + protected void error(Object obj, Throwable e) { + log.error(Objects.toString(obj), e); + } + + @Override + protected boolean isTraceEnabled() { + return log.isTraceEnabled(); + } + + @Override + protected void trace(Object obj) { + log.error(Objects.toString(obj)); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/SyncResult.java b/org.argeo.cms/src/org/argeo/cms/file/SyncResult.java new file mode 100644 index 000000000..35ab3f96a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/SyncResult.java @@ -0,0 +1,101 @@ +package org.argeo.cms.file; + +import java.time.Instant; +import java.util.Set; +import java.util.TreeSet; + +/** Describes what happendend during a sync operation. */ +public class SyncResult { + private final Set added = new TreeSet<>(); + private final Set modified = new TreeSet<>(); + private final Set deleted = new TreeSet<>(); + private final Set errors = new TreeSet<>(); + + public Set getAdded() { + return added; + } + + public Set getModified() { + return modified; + } + + public Set getDeleted() { + return deleted; + } + + public Set 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 { + private final T sourcePath;// if null this is a failed delete + private final T targetPath; + private final Exception exception; + private final Instant timestamp = Instant.now(); + + public Error(T sourcePath, T targetPath, Exception e) { + super(); + this.sourcePath = sourcePath; + this.targetPath = targetPath; + this.exception = e; + } + + public T getSourcePath() { + return sourcePath; + } + + public T getTargetPath() { + return targetPath; + } + + public Exception getException() { + return exception; + } + + public Instant getTimestamp() { + return timestamp; + } + + @Override + public int compareTo(Error o) { + return timestamp.compareTo(o.timestamp); + } + + @Override + public int hashCode() { + return timestamp.hashCode(); + } + + @Override + public String toString() { + return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " " + + targetPath + " " + exception.getMessage(); + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileStore.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileStore.java new file mode 100644 index 000000000..a4da893b6 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileStore.java @@ -0,0 +1,71 @@ +package org.argeo.cms.file.provider; + +import java.io.IOException; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; + +import org.argeo.api.acr.fs.AbstractFsStore; + +public class CmsFileStore extends AbstractFsStore { + + @Override + public String name() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String type() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isReadOnly() { + // TODO Auto-generated method stub + return false; + } + + @Override + public long getTotalSpace() throws IOException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getUsableSpace() throws IOException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getUnallocatedSpace() throws IOException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean supportsFileAttributeView(Class type) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsFileAttributeView(String name) { + // TODO Auto-generated method stub + return false; + } + + @Override + public V getFileStoreAttributeView(Class type) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object getAttribute(String attribute) throws IOException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystem.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystem.java new file mode 100644 index 000000000..6d4eea279 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystem.java @@ -0,0 +1,100 @@ +package org.argeo.cms.file.provider; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.WatchService; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.Set; + +import org.argeo.api.acr.fs.AbstractFsSystem; + +public class CmsFileSystem extends AbstractFsSystem { + + @Override + public CmsFileStore getBaseFileStore() { + // TODO Auto-generated method stub + return null; + } + + @Override + public CmsFileStore getFileStore(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public FileSystemProvider provider() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void close() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public boolean isOpen() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isReadOnly() { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getSeparator() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getRootDirectories() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterable getFileStores() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Set supportedFileAttributeViews() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Path getPath(String first, String... more) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + // TODO Auto-generated method stub + return null; + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + // TODO Auto-generated method stub + return null; + } + + @Override + public WatchService newWatchService() throws IOException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystemProvider.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystemProvider.java new file mode 100644 index 000000000..51eb84e71 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystemProvider.java @@ -0,0 +1,133 @@ +package org.argeo.cms.file.provider; + +import java.io.IOException; +import java.net.URI; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.spi.FileSystemProvider; +import java.util.Map; +import java.util.Set; + +public class CmsFileSystemProvider extends FileSystemProvider { + + @Override + public String getScheme() { + return "cms"; + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public FileSystem getFileSystem(URI uri) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Path getPath(URI uri) { + // TODO Auto-generated method stub + return null; + } + + @Override + public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) + throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public DirectoryStream newDirectoryStream(Path dir, Filter filter) throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void delete(Path path) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public boolean isSameFile(Path path, Path path2) throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isHidden(Path path) throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public FileStore getFileStore(Path path) throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void checkAccess(Path path, AccessMode... modes) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public V getFileAttributeView(Path path, Class type, LinkOption... options) { + // TODO Auto-generated method stub + return null; + } + + @Override + public A readAttributes(Path path, Class type, LinkOption... options) + throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { + // TODO Auto-generated method stub + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsPath.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsPath.java new file mode 100644 index 000000000..504e69bff --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsPath.java @@ -0,0 +1,25 @@ +package org.argeo.cms.file.provider; + +import org.argeo.api.acr.fs.AbstractFsPath; + +public class CmsPath extends AbstractFsPath { + + public CmsPath(CmsFileSystem filesSystem, CmsFileStore fileStore, String[] segments, boolean absolute) { + super(filesSystem, fileStore, segments, absolute); + } + + public CmsPath(CmsFileSystem filesSystem, String path) { + super(filesSystem, path); + } + + @Override + protected AbstractFsPath newInstance(String path) { + return new CmsPath(getFileSystem(), path); + } + + @Override + protected AbstractFsPath newInstance(String[] segments, boolean absolute) { + return new CmsPath(getFileSystem(), getFileStore(), segments, absolute); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/http/HttpHeader.java b/org.argeo.cms/src/org/argeo/cms/http/HttpHeader.java new file mode 100644 index 000000000..ef7385d1d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/http/HttpHeader.java @@ -0,0 +1,37 @@ +package org.argeo.cms.http; + +/** Selection of standard or common HTTP headers (including WebDav). */ +public enum HttpHeader { + AUTHORIZATION("Authorization"), // + WWW_AUTHENTICATE("WWW-Authenticate"), // + ALLOW("Allow"), // + VIA("Via"), // + + // WebDav + DAV("DAV"), // + DEPTH("Depth"), // + + // Non-standard + X_FORWARDED_HOST("X-Forwarded-Host"), // + ; + + public final static String BASIC = "Basic"; + public final static String REALM = "realm"; + public final static String NEGOTIATE = "Negotiate"; + + private final String name; + + private HttpHeader(String headerName) { + this.name = headerName; + } + + public String getHeaderName() { + return name; + } + + @Override + public String toString() { + return getHeaderName(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/http/HttpMethod.java b/org.argeo.cms/src/org/argeo/cms/http/HttpMethod.java new file mode 100644 index 000000000..786904564 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/http/HttpMethod.java @@ -0,0 +1,19 @@ +package org.argeo.cms.http; + +/** Generic HTTP methods. */ +public enum HttpMethod { + OPTIONS, // + HEAD, // + GET, // + POST, // + PUT, // + DELETE, // + + // WebDav + PROPFIND, // + PROPPATCH, // + MKCOL, // + MOVE, // + COPY, // + ; +} diff --git a/org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java b/org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java new file mode 100644 index 000000000..3b9a47a38 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java @@ -0,0 +1,66 @@ +package org.argeo.cms.http; + +/** + * Standard HTTP response status codes (including WebDav ones). + * + * @see "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" + */ +public enum HttpStatus { + // Successful responses (200–299) + OK(200, "OK"), // + NO_CONTENT(204, "No Content"), // + MULTI_STATUS(207, "Multi-Status"), // WebDav + // Client error responses (400–499) + UNAUTHORIZED(401, "Unauthorized"), // + FORBIDDEN(403, "Forbidden"), // + NOT_FOUND(404, "Not Found"), // + // Server error responses (500-599) + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), // + NOT_IMPLEMENTED(501, "Not Implemented"), // + ; + + private final int code; + private final String reasonPhrase; + + HttpStatus(int statusCode, String reasonPhrase) { + this.code = statusCode; + this.reasonPhrase = reasonPhrase; + } + + public int getCode() { + return code; + } + + public String getReasonPhrase() { + return reasonPhrase; + } + + /** + * The status line, as defined by RFC2616. + * + * @see "https://www.rfc-editor.org/rfc/rfc2616#section-6.1" + */ + public String getStatusLine(String httpVersion) { + return httpVersion + " " + code + " " + reasonPhrase; + } + + public static HttpStatus parseStatusLine(String statusLine) { + try { + String[] arr = statusLine.split(" "); + int code = Integer.parseInt(arr[1]); + for (HttpStatus status : values()) { + if (status.getCode() == code) + return status; + } + } catch (Exception e) { + throw new IllegalArgumentException("Invalid status line: " + statusLine, e); + } + throw new IllegalArgumentException("Unkown status code: " + statusLine); + } + + @Override + public String toString() { + return code + " " + reasonPhrase; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/http/server/HttpServerUtils.java b/org.argeo.cms/src/org/argeo/cms/http/server/HttpServerUtils.java new file mode 100644 index 000000000..ab033f0ce --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/http/server/HttpServerUtils.java @@ -0,0 +1,46 @@ +package org.argeo.cms.http.server; + +import java.net.URI; +import java.util.Objects; + +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; + +/** HTTP utilities on the server-side. */ +public class HttpServerUtils { + private final static String SLASH = "/"; + + private static String extractPathWithingContext(HttpContext httpContext, String fullPath, boolean startWithSlash) { + Objects.requireNonNull(fullPath); + String contextPath = httpContext.getPath(); + if (!fullPath.startsWith(contextPath)) + throw new IllegalArgumentException(fullPath + " does not belong to context" + contextPath); + String path = fullPath.substring(contextPath.length()); + // TODO optimise? + if (!startWithSlash && path.startsWith(SLASH)) { + path = path.substring(1); + } else if (startWithSlash && !path.startsWith(SLASH)) { + path = SLASH + path; + } + return path; + } + + /** Path within the context, NOT starting with a slash. */ + public static String relativize(HttpExchange exchange) { + URI uri = exchange.getRequestURI(); + HttpContext httpContext = exchange.getHttpContext(); + return extractPathWithingContext(httpContext, uri.getPath(), false); + } + + /** Path within the context, starting with a slash. */ + public static String subPath(HttpExchange exchange) { + URI uri = exchange.getRequestURI(); + HttpContext httpContext = exchange.getHttpContext(); + return extractPathWithingContext(httpContext, uri.getPath(), true); + } + + /** singleton */ + private HttpServerUtils() { + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java index aa3a6ad17..4f5a85ddf 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java @@ -1,102 +1,73 @@ package org.argeo.cms.internal.auth; import java.io.Serializable; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; +import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; -import javax.crypto.SecretKey; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.security.auth.x500.X500Principal; import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsSession; -import org.argeo.cms.security.NodeSecurityUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.ServiceRegistration; +import org.argeo.cms.internal.runtime.CmsContextImpl; import org.osgi.service.useradmin.Authorization; /** Default CMS session implementation. */ public class CmsSessionImpl implements CmsSession, Serializable { private static final long serialVersionUID = 1867719354246307225L; - private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext(); private final static CmsLog log = CmsLog.getLog(CmsSessionImpl.class); - // private final Subject initialSubject; - private transient AccessControlContext accessControlContext; + private transient Subject subject; private final UUID uuid; private final String localSessionId; private Authorization authorization; - private final LdapName userDn; +// private final LdapName userDn; + private final String userDn; private final boolean anonymous; private final ZonedDateTime creationTime; private ZonedDateTime end; private final Locale locale; - private ServiceRegistration serviceRegistration; - private Map views = new HashMap<>(); private List> onCloseCallbacks = Collections.synchronizedList(new ArrayList<>()); - public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) { + public CmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale, + String localSessionId) { + Objects.requireNonNull(uuid); + this.creationTime = ZonedDateTime.now(); this.locale = locale; - this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction() { - - @Override - public AccessControlContext run() { - return AccessController.getContext(); - } - - }); - // this.initialSubject = initialSubject; + this.subject = initialSubject; this.localSessionId = localSessionId; this.authorization = authorization; - if (authorization.getName() != null) - try { - this.userDn = new LdapName(authorization.getName()); - this.anonymous = false; - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Invalid user name " + authorization.getName(), e); - } - else { - this.userDn = NodeSecurityUtils.ROLE_ANONYMOUS_NAME; + if (authorization.getName() != null) { + this.userDn = authorization.getName(); + this.anonymous = false; + } else { + this.userDn = CmsConstants.ROLE_ANONYMOUS; this.anonymous = true; } - this.uuid = UUID.randomUUID(); - // register as service - Hashtable props = new Hashtable<>(); - props.put(CmsSession.USER_DN, userDn.toString()); - props.put(CmsSession.SESSION_UUID, uuid.toString()); - props.put(CmsSession.SESSION_LOCAL_ID, localSessionId); - serviceRegistration = bc.registerService(CmsSession.class, this, props); + this.uuid = uuid; } public void close() { end = ZonedDateTime.now(); - serviceRegistration.unregister(); + CmsContextImpl.getCmsContext().unregisterCmsSession(this); +// serviceRegistration.unregister(); for (Consumer onClose : onCloseCallbacks) { onClose.accept(this); @@ -105,15 +76,15 @@ public class CmsSessionImpl implements CmsSession, Serializable { try { LoginContext lc; if (isAnonymous()) { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, getSubject()); + lc = CmsAuth.ANONYMOUS.newLoginContext(getSubject()); } else { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, getSubject()); + lc = CmsAuth.USER.newLoginContext(getSubject()); } lc.logout(); } catch (LoginException e) { log.warn("Could not logout " + getSubject() + ": " + e); } finally { - accessControlContext = null; + subject = null; } log.debug("Closed " + this); } @@ -124,13 +95,13 @@ public class CmsSessionImpl implements CmsSession, Serializable { } public Subject getSubject() { - return Subject.getSubject(accessControlContext); + return subject; } - public Set getSecretKeys() { - checkValid(); - return getSubject().getPrivateCredentials(SecretKey.class); - } +// public Set getSecretKeys() { +// checkValid(); +// return getSubject().getPrivateCredentials(SecretKey.class); +// } @Override public boolean isValid() { @@ -162,7 +133,7 @@ public class CmsSessionImpl implements CmsSession, Serializable { } @Override - public LdapName getUserDn() { + public String getUserDn() { return userDn; } @@ -205,59 +176,6 @@ public class CmsSessionImpl implements CmsSession, Serializable { } public String toString() { - return "CMS Session " + userDn + " local=" + localSessionId + ", uuid=" + uuid; - } - - public static CmsSessionImpl getByLocalId(String localId) { - Collection> sr; - try { - sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")"); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Cannot get CMS session for id " + localId, e); - } - ServiceReference cmsSessionRef; - if (sr.size() == 1) { - cmsSessionRef = sr.iterator().next(); - return (CmsSessionImpl) bc.getService(cmsSessionRef); - } else if (sr.size() == 0) { - return null; - } else - throw new IllegalStateException(sr.size() + " CMS sessions registered for " + localId); - - } - - public static CmsSessionImpl getByUuid(Object uuid) { - Collection> 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 cmsSessionRef; - if (sr.size() == 1) { - cmsSessionRef = sr.iterator().next(); - return (CmsSessionImpl) bc.getService(cmsSessionRef); - } else if (sr.size() == 0) { - return null; - } else - throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid); - - } - - public static void closeInvalidSessions() { - Collection> srs; - try { - srs = bc.getServiceReferences(CmsSession.class, null); - for (ServiceReference sr : srs) { - CmsSession cmsSession = bc.getService(sr); - if (!cmsSession.isValid()) { - ((CmsSessionImpl) cmsSession).close(); - if (log.isDebugEnabled()) - log.debug("Closed expired CMS session " + cmsSession); - } - } - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Cannot get CMS sessions", e); - } + return "CMS Session " + userDn + " localId=" + localSessionId + ", uuid=" + uuid; } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java deleted file mode 100644 index 19136606d..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java +++ /dev/null @@ -1,491 +0,0 @@ -package org.argeo.cms.internal.auth; - -import static org.argeo.util.naming.LdapAttrs.cn; -import static org.argeo.util.naming.LdapAttrs.description; -import static org.argeo.util.naming.LdapAttrs.owner; - -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.security.auth.Subject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsUserManager; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.osgi.useradmin.TokenUtils; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.NamingUtils; -import org.argeo.util.naming.SharedSecret; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** - * Canonical implementation of the people {@link CmsUserManager}. Wraps - * interaction with users and groups. - * - * In a *READ-ONLY* mode. We want to be able to: - *
      - *
    • Retrieve my user and corresponding information (main info, - * groups...)
    • - *
    • List all local groups (not the system roles)
    • - *
    • If sufficient rights: retrieve a given user and its information
    • - *
    - */ -public class CmsUserManagerImpl implements CmsUserManager { - private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class); - - private UserAdmin userAdmin; -// private Map serviceProperties; - private WorkTransaction userTransaction; - - private Map> userDirectories = Collections - .synchronizedMap(new LinkedHashMap<>()); - - @Override - public String getMyMail() { - return getUserMail(CurrentUser.getUsername()); - } - - @Override - public Role[] getRoles(String filter) throws InvalidSyntaxException { - return userAdmin.getRoles(filter); - } - - // ALL USER: WARNING access to this will be later reduced - - /** Retrieve a user given his dn */ - public User getUser(String dn) { - return (User) getUserAdmin().getRole(dn); - } - - /** Can be a group or a user */ - public String getUserDisplayName(String dn) { - // FIXME: during initialisation phase, the system logs "admin" as user - // name rather than the corresponding dn - if ("admin".equals(dn)) - return "System Administrator"; - else - return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn); - } - - @Override - public String getUserMail(String dn) { - return UserAdminUtils.getUserMail(getUserAdmin(), dn); - } - - /** Lists all roles of the given user */ - @Override - public String[] getUserRoles(String dn) { - Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn)); - return currAuth.getRoles(); - } - - @Override - public boolean isUserInRole(String userDn, String roleDn) { - String[] roles = getUserRoles(userDn); - for (String role : roles) { - if (role.equalsIgnoreCase(roleDn)) - return true; - } - return false; - } - - private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(), - LdapAttrs.uid.name() }; - - public Set listUsersInGroup(String groupDn, String filter) { - Group group = (Group) userAdmin.getRole(groupDn); - if (group == null) - throw new IllegalArgumentException("Group " + groupDn + " not found"); - Set users = new HashSet(); - addUsers(users, group, filter); - return users; - } - - /** Recursively add users to list */ - private void addUsers(Set users, Group group, String filter) { - Role[] roles = group.getMembers(); - for (Role role : roles) { - if (role.getType() == Role.GROUP) { - addUsers(users, (Group) role, filter); - } else if (role.getType() == Role.USER) { - if (match(role, filter)) - users.add((User) role); - } else { - // ignore - } - } - } - - public List listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) { - Role[] roles = null; - try { - roles = getUserAdmin().getRoles(filter); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e); - } - - List users = new ArrayList(); - for (Role role : roles) { - if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role) - && (includeSystemRoles || !role.getName().toLowerCase().endsWith(CmsConstants.ROLES_BASEDN))) { - if (match(role, filter)) - users.add((User) role); - } - } - return users; - } - - private boolean match(Role role, String filter) { - boolean doFilter = filter != null && !"".equals(filter); - if (doFilter) { - for (String prop : knownProps) { - Object currProp = null; - try { - currProp = role.getProperties().get(prop); - } catch (Exception e) { - throw e; - } - if (currProp != null) { - String currPropStr = ((String) currProp).toLowerCase(); - if (currPropStr.contains(filter.toLowerCase())) { - return true; - } - } - } - return false; - } else - return true; - } - - @Override - public User getUserFromLocalId(String localId) { - User user = getUserAdmin().getUser(LdapAttrs.uid.name(), localId); - if (user == null) - user = getUserAdmin().getUser(LdapAttrs.cn.name(), localId); - return user; - } - - @Override - public String buildDefaultDN(String localId, int type) { - return buildDistinguishedName(localId, getDefaultDomainName(), type); - } - - @Override - public String getDefaultDomainName() { - Map dns = getKnownBaseDns(true); - if (dns.size() == 1) - return dns.keySet().iterator().next(); - else - throw new IllegalStateException("Current context contains " + dns.size() + " base dns: " - + dns.keySet().toString() + ". Unable to chose a default one."); - } - -// public Map getKnownBaseDns(boolean onlyWritable) { -// Map dns = new HashMap(); -// String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]); -// for (String uri : propertyKeys) { -// if (!uri.startsWith("/")) -// continue; -// Dictionary props = UserAdminConf.uriAsProperties(uri); -// String readOnly = UserAdminConf.readOnly.getValue(props); -// String baseDn = UserAdminConf.baseDn.getValue(props); -// -// if (onlyWritable && "true".equals(readOnly)) -// continue; -// if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN)) -// continue; -// if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN)) -// continue; -// dns.put(baseDn, uri); -// } -// return dns; -// } - - public Map getKnownBaseDns(boolean onlyWritable) { - Map dns = new HashMap(); - for (UserDirectory userDirectory : userDirectories.keySet()) { - Boolean readOnly = userDirectory.isReadOnly(); - String baseDn = userDirectory.getBaseDn().toString(); - - if (onlyWritable && readOnly) - continue; - if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN)) - continue; - if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN)) - continue; - dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString()); - - } - return dns; - } - - public String buildDistinguishedName(String localId, String baseDn, int type) { - Map dns = getKnownBaseDns(true); - Dictionary props = UserAdminConf.uriAsProperties(dns.get(baseDn)); - String dn = null; - if (Role.GROUP == type) - dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn; - else if (Role.USER == type) - dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn; - else - throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId); - return dn; - } - - @Override - public void changeOwnPassword(char[] oldPassword, char[] newPassword) { - String name = CurrentUser.getUsername(); - LdapName dn; - try { - dn = new LdapName(name); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Invalid user dn " + name, e); - } - User user = (User) userAdmin.getRole(dn.toString()); - if (!user.hasCredential(null, oldPassword)) - throw new IllegalArgumentException("Invalid password"); - if (Arrays.equals(newPassword, new char[0])) - throw new IllegalArgumentException("New password empty"); - try { - userTransaction.begin(); - user.getCredentials().put(null, newPassword); - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - log.error("Could not roll back", e1); - } - if (e instanceof RuntimeException) - throw (RuntimeException) e; - else - throw new RuntimeException("Cannot change password", e); - } - } - - public void resetPassword(String username, char[] newPassword) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Invalid user dn " + username, e); - } - User user = (User) userAdmin.getRole(dn.toString()); - if (Arrays.equals(newPassword, new char[0])) - throw new IllegalArgumentException("New password empty"); - try { - userTransaction.begin(); - user.getCredentials().put(null, newPassword); - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - log.error("Could not roll back", e1); - } - if (e instanceof RuntimeException) - throw (RuntimeException) e; - else - throw new RuntimeException("Cannot change password", e); - } - } - - public String addSharedSecret(String email, int hours) { - User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email); - try { - userTransaction.begin(); - String uuid = UUID.randomUUID().toString(); - SharedSecret sharedSecret = new SharedSecret(hours, uuid); - user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword()); - String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue(); - userTransaction.commit(); - return tokenStr; - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - log.error("Could not roll back", e1); - } - if (e instanceof RuntimeException) - throw (RuntimeException) e; - else - throw new RuntimeException("Cannot change password", e); - } - } - - @Deprecated - public String addSharedSecret(String username, String authInfo, String authToken) { - try { - userTransaction.begin(); - User user = (User) userAdmin.getRole(username); - SharedSecret sharedSecret = new SharedSecret(authInfo, authToken); - user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword()); - String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue(); - userTransaction.commit(); - return tokenStr; - } catch (Exception e1) { - try { - if (!userTransaction.isNoTransactionStatus()) - userTransaction.rollback(); - } catch (Exception e2) { - if (log.isTraceEnabled()) - log.trace("Cannot rollback transaction", e2); - } - throw new RuntimeException("Cannot add shared secret", e1); - } - } - - @Override - public void expireAuthToken(String token) { - try { - userTransaction.begin(); - String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN; - Group tokenGroup = (Group) userAdmin.getRole(dn); - String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC)); - tokenGroup.getProperties().put(description.name(), ldapDate); - userTransaction.commit(); - if (log.isDebugEnabled()) - log.debug("Token " + token + " expired."); - } catch (Exception e1) { - try { - if (!userTransaction.isNoTransactionStatus()) - userTransaction.rollback(); - } catch (Exception e2) { - if (log.isTraceEnabled()) - log.trace("Cannot rollback transaction", e2); - } - throw new RuntimeException("Cannot expire token", e1); - } - } - - @Override - public void expireAuthTokens(Subject subject) { - Set tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN); - for (String token : tokens) - expireAuthToken(token); - } - - @Override - public void addAuthToken(String userDn, String token, Integer hours, String... roles) { - addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles); - } - - @Override - public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) { - try { - userTransaction.begin(); - User user = (User) userAdmin.getRole(userDn); - String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN; - Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP); - if (roles != null) - for (String role : roles) { - Role r = userAdmin.getRole(role); - if (r != null) - tokenGroup.addMember(r); - else { - if (!role.equals(CmsConstants.ROLE_USER)) { - throw new IllegalStateException( - "Cannot add role " + role + " to token " + token + " for " + userDn); - } - } - } - tokenGroup.getProperties().put(owner.name(), user.getName()); - if (expiryDate != null) { - String ldapDate = NamingUtils.instantToLdapDate(expiryDate); - tokenGroup.getProperties().put(description.name(), ldapDate); - } - userTransaction.commit(); - } catch (Exception e1) { - try { - if (!userTransaction.isNoTransactionStatus()) - userTransaction.rollback(); - } catch (Exception e2) { - if (log.isTraceEnabled()) - log.trace("Cannot rollback transaction", e2); - } - throw new RuntimeException("Cannot add token", e1); - } - } - -// public User createUserFromPerson(Node person) { -// String email = JcrUtils.get(person, LdapAttrs.mail.property()); -// String dn = buildDefaultDN(email, Role.USER); -// User user; -// try { -// userTransaction.begin(); -// user = (User) userAdmin.createRole(dn, Role.USER); -// Dictionary userProperties = user.getProperties(); -// String name = JcrUtils.get(person, LdapAttrs.displayName.property()); -// userProperties.put(LdapAttrs.cn.name(), name); -// userProperties.put(LdapAttrs.displayName.name(), name); -// String givenName = JcrUtils.get(person, LdapAttrs.givenName.property()); -// String surname = JcrUtils.get(person, LdapAttrs.sn.property()); -// userProperties.put(LdapAttrs.givenName.name(), givenName); -// userProperties.put(LdapAttrs.sn.name(), surname); -// userProperties.put(LdapAttrs.mail.name(), email.toLowerCase()); -// userTransaction.commit(); -// } catch (Exception e) { -// try { -// userTransaction.rollback(); -// } catch (Exception e1) { -// log.error("Could not roll back", e1); -// } -// if (e instanceof RuntimeException) -// throw (RuntimeException) e; -// else -// throw new RuntimeException("Cannot create user", e); -// } -// return user; -// } - - public UserAdmin getUserAdmin() { - return userAdmin; - } - -// public UserTransaction getUserTransaction() { -// return userTransaction; -// } - - /* DEPENDENCY INJECTION */ - public void setUserAdmin(UserAdmin userAdmin) { - this.userAdmin = userAdmin; -// this.serviceProperties = serviceProperties; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - - public void addUserDirectory(UserDirectory userDirectory, Map properties) { - userDirectories.put(userDirectory, new Hashtable<>(properties)); - } - - public void removeUserDirectory(UserDirectory userDirectory, Map properties) { - userDirectories.remove(userDirectory); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java deleted file mode 100644 index 0979a2157..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.argeo.cms.internal.auth; - -import java.io.Console; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -/** Callback handler to be used with a command line UI. */ -public class ConsoleCallbackHandler implements CallbackHandler { - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - Console console = System.console(); - if (console == null) - throw new IllegalStateException("No console available"); - - PrintWriter writer = console.writer(); - for (int i = 0; i < callbacks.length; i++) { - if (callbacks[i] instanceof TextOutputCallback) { - TextOutputCallback callback = (TextOutputCallback) callbacks[i]; - writer.write(callback.getMessage()); - } else if (callbacks[i] instanceof NameCallback) { - NameCallback callback = (NameCallback) callbacks[i]; - writer.write(callback.getPrompt()); - if (callback.getDefaultName() != null) - writer.write(" (" + callback.getDefaultName() + ")"); - writer.write(" : "); - String answer = console.readLine(); - if (callback.getDefaultName() != null && answer.trim().equals("")) - callback.setName(callback.getDefaultName()); - else - callback.setName(answer); - } else if (callbacks[i] instanceof PasswordCallback) { - PasswordCallback callback = (PasswordCallback) callbacks[i]; - writer.write(callback.getPrompt()); - char[] answer = console.readPassword(); - callback.setPassword(answer); - Arrays.fill(answer, ' '); - } -// else if (callbacks[i] instanceof LocaleChoice) { -// LocaleChoice callback = (LocaleChoice) callbacks[i]; -// writer.write("Language"); -// writer.write("\n"); -// for (int j = 0; j < callback.getLocales().size(); j++) { -// Locale locale = callback.getLocales().get(j); -// writer.print(j + " : " + locale.getDisplayName() + "\n"); -// } -// writer.write("(" + callback.getDefaultIndex() + ") : "); -// String answer = console.readLine(); -// if (answer.trim().equals("")) -// callback.setSelectedIndex(callback.getDefaultIndex()); -// else -// callback.setSelectedIndex(new Integer(answer.trim())); -// } - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java index c75360129..9e0ebce97 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java @@ -1,17 +1,13 @@ package org.argeo.cms.internal.auth; import java.security.Principal; -import java.util.Collections; -import java.util.Dictionary; -import java.util.Enumeration; import java.util.HashSet; import java.util.Set; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; +import javax.xml.namespace.QName; +import org.argeo.cms.RoleNameUtils; import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; /** * A {@link Principal} which has been implied by an {@link Authorization}. If it @@ -21,67 +17,42 @@ import org.osgi.service.useradmin.Role; * identity is removed, the related {@link ImpliedByPrincipal}s can thus be * removed. */ -public final class ImpliedByPrincipal implements Principal, Role { - private final LdapName name; - private Set causes = new HashSet(); +public final class ImpliedByPrincipal implements Principal { + private final String name; + private final QName roleName; + private final boolean systemRole; + private final String context; - private int type = Role.ROLE; + private Set causes = new HashSet(); public ImpliedByPrincipal(String name, Principal userPrincipal) { - try { - this.name = new LdapName(name); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Badly formatted role name", e); - } - if (userPrincipal != null) - causes.add(userPrincipal); - } - - public ImpliedByPrincipal(LdapName name, Principal userPrincipal) { this.name = name; + roleName = RoleNameUtils.getLastRdnAsName(name); + systemRole = RoleNameUtils.isSystemRole(roleName); + context = RoleNameUtils.getContext(name); if (userPrincipal != null) causes.add(userPrincipal); } public String getName() { - return name.toString(); - } - - public boolean addMember(Principal user) { - throw new UnsupportedOperationException(); - } - - public boolean removeMember(Principal user) { - throw new UnsupportedOperationException(); - } - - public boolean isMember(Principal member) { - return causes.contains(member); - } - - public Enumeration members() { - return Collections.enumeration(causes); + return name; } /* - * USER ADMIN + * OBJECT */ - @Override - /** Type of {@link Role}, if known. */ - public int getType() { - return type; + public QName getRoleName() { + return roleName; } - @Override - /** Not supported for the time being. */ - public Dictionary getProperties() { - throw new UnsupportedOperationException(); + public String getContext() { + return context; } - /* - * OBJECT - */ + public boolean isSystemRole() { + return systemRole; + } @Override public int hashCode() { @@ -90,8 +61,6 @@ public final class ImpliedByPrincipal implements Principal, Role { @Override public boolean equals(Object obj) { - // if (this == obj) - // return true; if (obj instanceof ImpliedByPrincipal) { ImpliedByPrincipal that = (ImpliedByPrincipal) obj; // TODO check members too? @@ -102,7 +71,6 @@ public final class ImpliedByPrincipal implements Principal, Role { @Override public String toString() { - // return name.toString() + " implied by " + causes; return name.toString(); } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java new file mode 100644 index 000000000..23f5d8427 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java @@ -0,0 +1,32 @@ +package org.argeo.cms.internal.auth; + +import java.util.Locale; +import java.util.UUID; + +import javax.security.auth.Subject; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthSession; +import org.osgi.service.useradmin.Authorization; + +/** CMS session implementation in a web context. */ +public class RemoteCmsSessionImpl extends CmsSessionImpl { + private static final long serialVersionUID = -5178883380637048025L; + private RemoteAuthSession httpSession; + + public RemoteCmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale, + RemoteAuthRequest request) { + super(uuid, initialSubject, authorization, locale, + request.getSession() != null ? request.getSession().getId() : null); + httpSession = request.getSession(); + } + + @Override + public boolean isValid() { + if (isClosed()) + return false; + if (httpSession == null) + return true; + return httpSession.isValid(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java new file mode 100644 index 000000000..e17a089fe --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java @@ -0,0 +1,59 @@ +package org.argeo.cms.internal.http; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; +import org.argeo.cms.auth.RemoteAuthUtils; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +/** An {@link Authenticator} implementation based on CMS authentication. */ +public class CmsAuthenticator extends Authenticator { + // TODO make it configurable + private final String httpAuthRealm = "Argeo"; + private final boolean forceBasic = false; + + @Override + public Result authenticate(HttpExchange exch) { + RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(exch); + ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader()); + LoginContext lc; + try { + lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange)); + lc.login(); + } catch (LoginException e) { + if (authIsRequired(remoteAuthExchange, remoteAuthExchange)) { + int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthExchange, remoteAuthExchange, httpAuthRealm, + forceBasic); + return new Authenticator.Retry(statusCode); + + } else { + lc = RemoteAuthUtils.anonymousLogin(remoteAuthExchange, remoteAuthExchange); + } + if (lc == null) + return new Authenticator.Failure(403); + } finally { + Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader); + } + + Subject subject = lc.getSubject(); + + String username = CurrentUser.getUsername(subject); + HttpPrincipal httpPrincipal = new HttpPrincipal(username, httpAuthRealm); + return new Authenticator.Success(httpPrincipal); + } + + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { + return true; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java deleted file mode 100644 index c888c291b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.internal.http; - -/** Compatible with Jetty. */ -public interface InternalHttpConstants { - static final String HTTP_ENABLED = "http.enabled"; - static final String HTTP_PORT = "http.port"; - static final String HTTP_HOST = "http.host"; - static final String HTTPS_ENABLED = "https.enabled"; - static final String HTTPS_HOST = "https.host"; - static final String HTTPS_PORT = "https.port"; - static final String SSL_KEYSTORE = "ssl.keystore"; - static final String SSL_PASSWORD = "ssl.password"; - static final String SSL_KEYPASSWORD = "ssl.keypassword"; - static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth"; - static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth"; - static final String SSL_PROTOCOL = "ssl.protocol"; - static final String SSL_ALGORITHM = "ssl.algorithm"; - static final String SSL_KEYSTORETYPE = "ssl.keystoretype"; - static final String JETTY_PROPERTY_PREFIX = "org.eclipse.equinox.http.jetty."; - // Argeo specific - static final String WEBSOCKET_ENABLED = "websocket.enabled"; - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PublicCmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PublicCmsAuthenticator.java new file mode 100644 index 000000000..14d79a63d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/PublicCmsAuthenticator.java @@ -0,0 +1,13 @@ +package org.argeo.cms.internal.http; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; + +public class PublicCmsAuthenticator extends CmsAuthenticator { + + @Override + protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) { + return false; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java b/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java new file mode 100644 index 000000000..b7e670c79 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java @@ -0,0 +1,85 @@ +package org.argeo.cms.internal.http; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthResponse; +import org.argeo.cms.auth.RemoteAuthSession; + +import com.sun.net.httpserver.HttpExchange; + +public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResponse { + private final HttpExchange httpExchange; + private RemoteAuthSession remoteAuthSession; + + public RemoteAuthHttpExchange(HttpExchange httpExchange) { + this.httpExchange = httpExchange; + this.remoteAuthSession = (RemoteAuthSession) httpExchange.getAttribute(RemoteAuthSession.class.getName()); + Objects.requireNonNull(this.remoteAuthSession); + } + + @Override + public void setHeader(String headerName, String value) { + httpExchange.getResponseHeaders().put(headerName, Collections.singletonList(value)); + } + + @Override + public void addHeader(String headerName, String value) { + List values = httpExchange.getResponseHeaders().getOrDefault(headerName, new ArrayList<>()); + values.add(value); + } + + @Override + public RemoteAuthSession getSession() { + return remoteAuthSession; + } + + @Override + public RemoteAuthSession createSession() { + throw new UnsupportedOperationException("Cannot create remote session"); + } + + @Override + public Locale getLocale() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object getAttribute(String key) { + return httpExchange.getAttribute(key); + } + + @Override + public void setAttribute(String key, Object object) { + httpExchange.setAttribute(key, object); + } + + @Override + public String getHeader(String key) { + List lst = httpExchange.getRequestHeaders().get(key); + if (lst == null || lst.size() == 0) + return null; + return lst.get(0); + } + + @Override + public int getLocalPort() { + return httpExchange.getLocalAddress().getPort(); + } + + @Override + public String getRemoteAddr() { + return httpExchange.getRemoteAddress().getHostName(); + } + + @Override + public int getRemotePort() { + return httpExchange.getRemoteAddress().getPort(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java deleted file mode 100644 index fd51c597a..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.internal.http; - -import java.util.Locale; - -import javax.security.auth.Subject; - -import org.argeo.cms.auth.RemoteAuthRequest; -import org.argeo.cms.auth.RemoteAuthSession; -import org.argeo.cms.internal.auth.CmsSessionImpl; -import org.osgi.service.useradmin.Authorization; - -/** CMS session implementation in a web context. */ -public class WebCmsSessionImpl extends CmsSessionImpl { - private static final long serialVersionUID = -5178883380637048025L; - private RemoteAuthSession httpSession; - - public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, - RemoteAuthRequest request) { - super(initialSubject, authorization, locale, request.getSession().getId()); - httpSession = request.getSession(); - } - - @Override - public boolean isValid() { - if (isClosed()) - return false; - return httpSession.isValid(); - } - - public static CmsSessionImpl getCmsSession(RemoteAuthRequest request) { - return CmsSessionImpl.getByLocalId(request.getSession().getId()); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java deleted file mode 100644 index 4a9392c83..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.argeo.cms.internal.http.client; - -import org.apache.commons.httpclient.Credentials; -import org.apache.commons.httpclient.auth.AuthScheme; -import org.apache.commons.httpclient.auth.CredentialsNotAvailableException; -import org.apache.commons.httpclient.auth.CredentialsProvider; - -/** SPNEGO credential provider */ -public class HttpCredentialProvider implements CredentialsProvider { - - @Override - public Credentials getCredentials(AuthScheme scheme, String host, int port, boolean proxy) - throws CredentialsNotAvailableException { - if (scheme instanceof SpnegoAuthScheme) - return new SpnegoCredentials(); - else - throw new UnsupportedOperationException("Auth scheme " + scheme.getSchemeName() + " not supported"); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java deleted file mode 100644 index 4abdd1458..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.argeo.cms.internal.http.client; - -import java.net.URL; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.Base64; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; - -import org.apache.commons.httpclient.Credentials; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpMethod; -import org.apache.commons.httpclient.URIException; -import org.apache.commons.httpclient.auth.AuthPolicy; -import org.apache.commons.httpclient.auth.AuthScheme; -import org.apache.commons.httpclient.auth.AuthenticationException; -import org.apache.commons.httpclient.auth.CredentialsProvider; -import org.apache.commons.httpclient.auth.MalformedChallengeException; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.params.DefaultHttpParams; -import org.apache.commons.httpclient.params.HttpParams; -import org.argeo.cms.internal.runtime.KernelConstants; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; - -/** Implementation of the SPNEGO auth scheme. */ -public class SpnegoAuthScheme implements AuthScheme { -// private final static Log log = LogFactory.getLog(SpnegoAuthScheme.class); - - public static final String NAME = "Negotiate"; - private final static Oid KERBEROS_OID; - static { - try { - KERBEROS_OID = new Oid("1.3.6.1.5.5.2"); - } catch (GSSException e) { - throw new IllegalStateException("Cannot create Kerberos OID", e); - } - } - - private boolean complete = false; - private String realm; - - @Override - public void processChallenge(String challenge) throws MalformedChallengeException { - // if(tokenStr!=null){ - // log.error("Received challenge while there is a token. Failing."); - // complete = false; - // } - - } - - @Override - public String getSchemeName() { - return NAME; - } - - @Override - public String getParameter(String name) { - return null; - } - - @Override - public String getRealm() { - return realm; - } - - @Override - public String getID() { - return NAME; - } - - @Override - public boolean isConnectionBased() { - return true; - } - - @Override - public boolean isComplete() { - return complete; - } - - @Override - public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { - // log.debug("authenticate " + method + " " + uri); - // return null; - throw new UnsupportedOperationException(); - } - - @Override - public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException { - GSSContext context = null; - String tokenStr = null; - String hostname; - try { - hostname = method.getURI().getHost(); - } catch (URIException e1) { - throw new IllegalStateException("Cannot authenticate", e1); - } - String serverPrinc = KernelConstants.DEFAULT_KERBEROS_SERVICE + "@" + hostname; - - try { - // Get service's principal name - GSSManager manager = GSSManager.getInstance(); - GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID); - - // Get the context for authentication - context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME); - // context.requestMutualAuth(true); // Request mutual authentication - // context.requestConf(true); // Request confidentiality - context.requestCredDeleg(true); - - byte[] token = new byte[0]; - - // token is ignored on the first call - token = context.initSecContext(token, 0, token.length); - - // Send a token to the server if one was generated by - // initSecContext - if (token != null) { - tokenStr = Base64.getEncoder().encodeToString(token); - // complete=true; - } - return "Negotiate " + tokenStr; - } catch (GSSException e) { - complete = true; - throw new AuthenticationException("Cannot authenticate to " + serverPrinc, e); - } - } - - public static void main(String[] args) { - if (args.length == 0) { - System.err.println("usage: java " + SpnegoAuthScheme.class.getName() + " "); - System.exit(1); - return; - } - String url = args[0]; - - URL jaasUrl = SpnegoAuthScheme.class.getResource("jaas.cfg"); - System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm()); - try { - LoginContext lc = new LoginContext("SINGLE_USER"); - lc.login(); - - AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class); - HttpParams params = DefaultHttpParams.getDefaultParams(); - ArrayList schemes = new ArrayList<>(); - schemes.add(SpnegoAuthScheme.NAME); - params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes); - params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider()); - - int responseCode = Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction() { - public Integer run() throws Exception { - HttpClient httpClient = new HttpClient(); - return httpClient.executeMethod(new GetMethod(url)); - } - }); - System.out.println("Reponse code: " + responseCode); - } catch (Exception e) { - e.printStackTrace(); - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java deleted file mode 100644 index f59b72a0b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.cms.internal.http.client; - -import org.apache.commons.httpclient.Credentials; - -public class SpnegoCredentials implements Credentials { - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg deleted file mode 100644 index 21176b911..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg +++ /dev/null @@ -1,5 +0,0 @@ -SINGLE_USER { - com.sun.security.auth.module.Krb5LoginModule optional - principal="${user.name}" - useTicketCache=true; -}; 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 index 038d7029c..b09956203 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java @@ -4,20 +4,15 @@ import java.security.AllPermission; import java.util.Dictionary; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoLogger; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceReference; import org.osgi.service.condpermadmin.BundleLocationCondition; import org.osgi.service.condpermadmin.ConditionInfo; import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; import org.osgi.service.condpermadmin.ConditionalPermissionInfo; import org.osgi.service.condpermadmin.ConditionalPermissionUpdate; -import org.osgi.service.http.HttpService; -import org.osgi.service.log.LogReaderService; import org.osgi.service.permissionadmin.PermissionInfo; -import org.osgi.util.tracker.ServiceTracker; /** * Activates the kernel. Gives access to kernel information for the rest of the @@ -26,75 +21,26 @@ import org.osgi.util.tracker.ServiceTracker; public class CmsActivator implements BundleActivator { private final static CmsLog log = CmsLog.getLog(CmsActivator.class); -// private static Activator instance; - // TODO make it configurable private boolean hardened = false; private static BundleContext bundleContext; - private LogReaderService logReaderService; - - private CmsOsgiLogger logger; -// private CmsStateImpl nodeState; -// private CmsDeploymentImpl nodeDeployment; -// private CmsContextImpl nodeInstance; - -// private ServiceTracker userAdminSt; - -// static { -// Bundle bundle = FrameworkUtil.getBundle(Activator.class); -// if (bundle != null) { -// bundleContext = bundle.getBundleContext(); -// } -// } - void init() { -// Runtime.getRuntime().addShutdownHook(new CmsShutdown()); -// instance = this; -// this.bc = bundleContext; - if (bundleContext != null) - this.logReaderService = getService(LogReaderService.class); - initArgeoLogger(); -// this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); -// -// try { -// initSecurity(); -//// initArgeoLogger(); -// initNode(); -// -// if (log.isTraceEnabled()) -// log.trace("Kernel bundle started"); -// } catch (Throwable e) { -// log.error("## FATAL: CMS activator failed", e); -// } } void destroy() { try { -// if (nodeInstance != null) -// nodeInstance.shutdown(); -// if (nodeDeployment != null) -// nodeDeployment.shutdown(); -// if (nodeState != null) -// nodeState.shutdown(); -// -// if (userAdminSt != null) -// userAdminSt.close(); - -// internalExecutorService.shutdown(); -// instance = null; bundleContext = null; - this.logReaderService = null; - // this.configurationAdmin = null; +// this.logReaderService = null; } catch (Exception e) { log.error("CMS activator shutdown failed", e); } - + new GogoShellKiller().start(); } - private void initSecurity() { + protected void initSecurity() { // code-level permissions String osgiSecurity = bundleContext.getProperty(Constants.FRAMEWORK_SECURITY); if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) { @@ -130,26 +76,6 @@ public class CmsActivator implements BundleActivator { } - private void initArgeoLogger() { - logger = new CmsOsgiLogger(logReaderService); - if (bundleContext != null) - bundleContext.registerService(ArgeoLogger.class, logger, null); - } - -// private void initNode() throws IOException { -// // Node state -// nodeState = new CmsStateImpl(); -// registerService(CmsState.class, nodeState, null); -// -// // Node deployment -// nodeDeployment = new CmsDeploymentImpl(); -//// registerService(NodeDeployment.class, nodeDeployment, null); -// -// // Node instance -// nodeInstance = new CmsContextImpl(); -// registerService(CmsContext.class, nodeInstance, null); -// } - public static void registerService(Class clss, T service, Dictionary properties) { if (bundleContext != null) { bundleContext.registerService(clss, service, properties); @@ -172,99 +98,18 @@ public class CmsActivator implements BundleActivator { @Override public void start(BundleContext bc) throws Exception { bundleContext = bc; -// if (!bc.getBundle().equals(bundleContext.getBundle())) -// throw new IllegalStateException( -// "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle()); - init(); -// userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null); -// userAdminSt.open(); - ServiceTracker httpSt = new ServiceTracker(bc, HttpService.class, null) { - - @Override - public HttpService addingService(ServiceReference sr) { - Object httpPort = sr.getProperty("http.port"); - Object httpsPort = sr.getProperty("https.port"); - log.info(httpPortsMsg(httpPort, httpsPort)); - close(); - return super.addingService(sr); - } - }; - httpSt.open(); - } + init(); - private String httpPortsMsg(Object httpPort, Object httpsPort) { - return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : ""); } @Override public void stop(BundleContext bc) throws Exception { -// if (!bc.getBundle().equals(bundleContext.getBundle())) -// throw new IllegalStateException( -// "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle()); + destroy(); bundleContext = null; } -// private T getService(Class clazz) { -// ServiceReference 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 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 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 index 6898c4348..85f045bae 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java @@ -1,34 +1,11 @@ package org.argeo.cms.internal.osgi; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.StandardWatchEventKinds; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoLogListener; -import org.argeo.cms.ArgeoLogger; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.internal.runtime.KernelConstants; -import org.argeo.osgi.useradmin.UserAdminConf; +import org.argeo.cms.runtime.DirectoryConf; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; @@ -38,48 +15,49 @@ import org.osgi.service.log.LogLevel; import org.osgi.service.log.LogListener; import org.osgi.service.log.LogReaderService; -/** Not meant to be used directly in standard log4j config */ -public class CmsOsgiLogger implements ArgeoLogger, LogListener { - /** Internal debug for development purposes. */ - private static Boolean debug = false; +/** Logs OSGi events. */ +public class CmsOsgiLogger implements LogListener { + private final static String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern"; + private final static String CONTEXT_NAME_PROP = "contextName"; + + private LogReaderService logReaderService; - private Boolean disabled = false; +// /** Internal debug for development purposes. */ +// private static Boolean debug = false; - private String level = null; +// private Boolean disabled = false; +// +// private String level = null; // private Level log4jLevel = null; - private Properties configuration; - - private AppenderImpl appender; - - private final List everythingListeners = Collections - .synchronizedList(new ArrayList()); - private final List allUsersListeners = Collections - .synchronizedList(new ArrayList()); - private final Map> userListeners = Collections - .synchronizedMap(new HashMap>()); +// private Properties configuration; - private BlockingQueue events; - private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); +// private AppenderImpl appender; - private Integer maxLastEventsCount = 10 * 1000; +// private BlockingQueue events; +// private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); - /** Marker to prevent stack overflow */ - private ThreadLocal dispatching = new ThreadLocal() { +// private Integer maxLastEventsCount = 10 * 1000; +// +// /** Marker to prevent stack overflow */ +// private ThreadLocal dispatching = new ThreadLocal() { +// +// @Override +// protected Boolean initialValue() { +// return false; +// } +// }; - @Override - protected Boolean initialValue() { - return false; - } - }; +// public CmsOsgiLogger(LogReaderService lrs) { +// } - public CmsOsgiLogger(LogReaderService lrs) { - if (lrs != null) { - Enumeration logEntries = lrs.getLog(); + public void start() { + if (logReaderService != null) { + Enumeration logEntries = logReaderService.getLog(); while (logEntries.hasMoreElements()) logged(logEntries.nextElement()); - lrs.addLogListener(this); + logReaderService.addLogListener(this); // configure log4j watcher // String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration"); @@ -103,43 +81,31 @@ public class CmsOsgiLogger implements ArgeoLogger, LogListener { // } // } } +// try { +//// events = new LinkedBlockingQueue(); +//// +//// // if (layout != null) +//// // setLayout(layout); +//// // else +//// // setLayout(new PatternLayout(pattern)); +////// appender = new AppenderImpl(); +//// reloadConfiguration(); +////// Logger.getRootLogger().addAppender(appender); +//// +//// logDispatcherThread = new LogDispatcherThread(); +//// logDispatcherThread.start(); +// } catch (Exception e) { +// throw new IllegalStateException("Cannot initialize log4j"); +// } } - public void init() { - try { - events = new LinkedBlockingQueue(); - - // 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 lst : userListeners.values()) - lst.clear(); - userListeners.clear(); - - events.clear(); - events = null; - logDispatcherThread.interrupt(); + public void stop() throws Exception { +// events.clear(); +// events = null; +// logDispatcherThread.interrupt(); + logReaderService.removeLogListener(this); } - // public void setLayout(Layout layout) { - // this.layout = layout; - // } - public String toString() { return "Node Logger"; } @@ -203,24 +169,23 @@ public class CmsOsgiLogger implements ArgeoLogger, LogListener { // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid); // } // servlets - Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP); + Object whiteBoardPattern = sr.getProperty(WHITEBOARD_PATTERN_PROP); if (whiteBoardPattern != null) { if (whiteBoardPattern instanceof String) { - sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern); + sb.append(" " + WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern); } else { - sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " - + arrayToString((String[]) whiteBoardPattern)); + sb.append(" " + WHITEBOARD_PATTERN_PROP + ": " + arrayToString((String[]) whiteBoardPattern)); } } // RWT - Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP); + Object contextName = sr.getProperty(CONTEXT_NAME_PROP); if (contextName != null) - sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName); + sb.append(" " + CONTEXT_NAME_PROP + ": " + contextName); // user directories - Object baseDn = sr.getProperty(UserAdminConf.baseDn.name()); + Object baseDn = sr.getProperty(DirectoryConf.baseDn.name()); if (baseDn != null) - sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn); + sb.append(" " + DirectoryConf.baseDn.name() + ": " + baseDn); } return sb.toString(); @@ -247,295 +212,300 @@ public class CmsOsgiLogger implements ArgeoLogger, LogListener { return false; } + public void setLogReaderService(LogReaderService logReaderService) { + this.logReaderService = logReaderService; + } + + // // ARGEO LOGGER // - public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) { - String username = CurrentUser.getUsername(); - if (username == null) - throw new CmsException("Only authenticated users can register a log listener"); - - if (!userListeners.containsKey(username)) { - List lst = Collections.synchronizedList(new ArrayList()); - userListeners.put(username, lst); - } - userListeners.get(username).add(listener); - List 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 lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents); - for (LogEvent evt : lastEvents) - if (everything || evt.getUsername() != null) - dispatchEvent(listener, evt); - } - - public synchronized void unregister(ArgeoLogListener listener) { - String username = CurrentUser.getUsername(); - if (username == null)// FIXME - return; - if (!userListeners.containsKey(username)) - throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); - if (!userListeners.get(username).contains(listener)) - throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); - userListeners.get(username).remove(listener); - if (userListeners.get(username).isEmpty()) - userListeners.remove(username); - - } - - public synchronized void unregisterForAll(ArgeoLogListener listener) { - everythingListeners.remove(listener); - allUsersListeners.remove(listener); - } - - /** For development purpose, since using regular logging is not easy here */ - private static void stdOut(Object obj) { - System.out.println(obj); - } - - private static void stdErr(Object obj) { - System.err.println(obj); - } - - private static void debug(Object obj) { - if (debug) - System.out.println(obj); - } - - private static boolean isInternalDebugEnabled() { - return debug; - } +// public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) { +// String username = CurrentUser.getUsername(); +// if (username == null) +// throw new IllegalStateException("Only authenticated users can register a log listener"); +// +// if (!userListeners.containsKey(username)) { +// List lst = Collections.synchronizedList(new ArrayList()); +// userListeners.put(username, lst); +// } +// userListeners.get(username).add(listener); +// List 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 lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents); +// for (LogEvent evt : lastEvents) +// if (everything || evt.getUsername() != null) +// dispatchEvent(listener, evt); +// } +// +// public synchronized void unregister(ArgeoLogListener listener) { +// String username = CurrentUser.getUsername(); +// if (username == null)// FIXME +// return; +// if (!userListeners.containsKey(username)) +// throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); +// if (!userListeners.get(username).contains(listener)) +// throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); +// userListeners.get(username).remove(listener); +// if (userListeners.get(username).isEmpty()) +// userListeners.remove(username); +// +// } +// +// public synchronized void unregisterForAll(ArgeoLogListener listener) { +// everythingListeners.remove(listener); +// allUsersListeners.remove(listener); +// } + +// /** For development purpose, since using regular logging is not easy here */ +// private static void stdOut(Object obj) { +// System.out.println(obj); +// } +// +// private static void stdErr(Object obj) { +// System.err.println(obj); +// } +// +// private static void debug(Object obj) { +// if (debug) +// System.out.println(obj); +// } +// +// private static boolean isInternalDebugEnabled() { +// return debug; +// } // public void setPattern(String pattern) { // this.pattern = pattern; // } - public void setDisabled(Boolean disabled) { - this.disabled = disabled; - } - - public void setLevel(String level) { - this.level = level; - } - - public void setConfiguration(Properties configuration) { - this.configuration = configuration; - } - - public void updateConfiguration(Properties configuration) { - setConfiguration(configuration); - reloadConfiguration(); - } - - public Properties getConfiguration() { - return configuration; - } - - /** - * Reloads configuration (if the configuration {@link Properties} is set) - */ - protected void reloadConfiguration() { - if (configuration != null) { -// LogManager.resetConfiguration(); -// PropertyConfigurator.configure(configuration); - } - } - - protected synchronized void processLoggingEvent(LogEvent event) { - if (disabled) - return; - - if (dispatching.get()) - return; - - if (level != null && !level.trim().equals("")) { -// if (log4jLevel == null || !log4jLevel.toString().equals(level)) -// try { -// log4jLevel = Level.toLevel(level); -// } catch (Exception e) { -// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null."); -// e.printStackTrace(); -// level = null; -// } +// public void setDisabled(Boolean disabled) { +// this.disabled = disabled; +// } // -// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) { -// return; -// } - } - - try { - // admin listeners - Iterator everythingIt = everythingListeners.iterator(); - while (everythingIt.hasNext()) - dispatchEvent(everythingIt.next(), event); - - if (event.getUsername() != null) { - Iterator allUsersIt = allUsersListeners.iterator(); - while (allUsersIt.hasNext()) - dispatchEvent(allUsersIt.next(), event); - - if (userListeners.containsKey(event.getUsername())) { - Iterator userIt = userListeners.get(event.getUsername()).iterator(); - while (userIt.hasNext()) - dispatchEvent(userIt.next(), event); - } - } - } catch (Exception e) { - stdOut("Cannot process logging event"); - e.printStackTrace(); - } - } - - protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { -// LoggingEvent event = evt.getLoggingEvent(); -// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(), -// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep()); - } +// public void setLevel(String level) { +// this.level = level; +// } - private class AppenderImpl { // extends AppenderSkeleton { - public boolean requiresLayout() { - return false; - } +// public void setConfiguration(Properties configuration) { +// this.configuration = configuration; +// } +// +// public void updateConfiguration(Properties configuration) { +// setConfiguration(configuration); +// reloadConfiguration(); +// } +// +// public Properties getConfiguration() { +// return configuration; +// } +// +// /** +// * Reloads configuration (if the configuration {@link Properties} is set) +// */ +// protected void reloadConfiguration() { +// if (configuration != null) { +//// LogManager.resetConfiguration(); +//// PropertyConfigurator.configure(configuration); +// } +// } - public void close() { - } +// protected synchronized void processLoggingEvent(LogEvent event) { +// if (disabled) +// return; +// +// if (dispatching.get()) +// return; +// +// if (level != null && !level.trim().equals("")) { +//// if (log4jLevel == null || !log4jLevel.toString().equals(level)) +//// try { +//// log4jLevel = Level.toLevel(level); +//// } catch (Exception e) { +//// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null."); +//// e.printStackTrace(); +//// level = null; +//// } +//// +//// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) { +//// return; +//// } +// } +// +//// try { +//// // admin listeners +//// Iterator everythingIt = everythingListeners.iterator(); +//// while (everythingIt.hasNext()) +//// dispatchEvent(everythingIt.next(), event); +//// +//// if (event.getUsername() != null) { +//// Iterator allUsersIt = allUsersListeners.iterator(); +//// while (allUsersIt.hasNext()) +//// dispatchEvent(allUsersIt.next(), event); +//// +//// if (userListeners.containsKey(event.getUsername())) { +//// Iterator userIt = userListeners.get(event.getUsername()).iterator(); +//// while (userIt.hasNext()) +//// dispatchEvent(userIt.next(), event); +//// } +//// } +//// } catch (Exception e) { +//// stdOut("Cannot process logging event"); +//// e.printStackTrace(); +//// } +// } + +// protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { +//// LoggingEvent event = evt.getLoggingEvent(); +//// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(), +//// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep()); +// } + +// private class AppenderImpl { // extends AppenderSkeleton { +// public boolean requiresLayout() { +// return false; +// } +// +// public void close() { +// } +// +//// @Override +//// protected void append(LoggingEvent event) { +//// if (events != null) { +//// try { +//// String username = CurrentUser.getUsername(); +//// events.put(new LogEvent(username, event)); +//// } catch (InterruptedException e) { +//// // silent +//// } +//// } +//// } +// +// } -// @Override -// protected void append(LoggingEvent event) { -// if (events != null) { +// private class LogDispatcherThread extends Thread { +// /** encapsulated in order to simplify concurrency management */ +// private LinkedList lastEvents = new LinkedList(); +// +// public LogDispatcherThread() { +// super("Argeo Logging Dispatcher Thread"); +// } +// +// public void run() { +// while (events != null) { // try { -// String username = CurrentUser.getUsername(); -// events.put(new LogEvent(username, event)); +// LogEvent loggingEvent = events.take(); +// processLoggingEvent(loggingEvent); +// addLastEvent(loggingEvent); // } catch (InterruptedException e) { -// // silent +// if (events == null) +// return; // } // } // } +// +// protected synchronized void addLastEvent(LogEvent loggingEvent) { +// if (lastEvents.size() >= maxLastEventsCount) +// lastEvents.poll(); +// lastEvents.add(loggingEvent); +// } +// +// public synchronized List getLastEvents(String username, Integer maxCount) { +// LinkedList evts = new LinkedList(); +// ListIterator it = lastEvents.listIterator(lastEvents.size()); +// int count = 0; +// while (it.hasPrevious() && (count < maxCount)) { +// LogEvent evt = it.previous(); +// if (username == null || username.equals(evt.getUsername())) { +// evts.push(evt); +// count++; +// } +// } +// return evts; +// } +// } - } - - private class LogDispatcherThread extends Thread { - /** encapsulated in order to simplify concurrency management */ - private LinkedList lastEvents = new LinkedList(); - - 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 getLastEvents(String username, Integer maxCount) { - LinkedList evts = new LinkedList(); - ListIterator it = lastEvents.listIterator(lastEvents.size()); - int count = 0; - while (it.hasPrevious() && (count < maxCount)) { - LogEvent evt = it.previous(); - if (username == null || username.equals(evt.getUsername())) { - evts.push(evt); - count++; - } - } - return evts; - } - } - - private class LogEvent { - private final String username; -// private final LoggingEvent loggingEvent; - - public LogEvent(String username) { - super(); - this.username = username; -// this.loggingEvent = loggingEvent; - } - -// @Override -// public int hashCode() { -// return loggingEvent.hashCode(); +// private class LogEvent { +// private final String username; +//// private final LoggingEvent loggingEvent; +// +// public LogEvent(String username) { +// super(); +// this.username = username; +//// this.loggingEvent = loggingEvent; // } // -// @Override -// public boolean equals(Object obj) { -// return loggingEvent.equals(obj); +//// @Override +//// public int hashCode() { +//// return loggingEvent.hashCode(); +//// } +//// +//// @Override +//// public boolean equals(Object obj) { +//// return loggingEvent.equals(obj); +//// } +//// +//// @Override +//// public String toString() { +//// return username + "@ " + loggingEvent.toString(); +//// } +// +// public String getUsername() { +// return username; // } // -// @Override -// public String toString() { -// return username + "@ " + loggingEvent.toString(); +//// public LoggingEvent getLoggingEvent() { +//// return loggingEvent; +//// } +// +// } +// +// private class Log4jConfWatcherThread extends Thread { +// private Path log4jConfigurationPath; +// +// public Log4jConfWatcherThread(Path log4jConfigurationPath) { +// super("Log4j Configuration Watcher"); +// try { +// this.log4jConfigurationPath = log4jConfigurationPath.toRealPath(); +// } catch (IOException e) { +// this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath(); +// stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage()); +// } // } - - public String getUsername() { - return username; - } - -// public LoggingEvent getLoggingEvent() { -// return loggingEvent; +// +// public void run() { +// Path parentDir = log4jConfigurationPath.getParent(); +// try (final WatchService watchService = FileSystems.getDefault().newWatchService()) { +// parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); +// WatchKey wk; +// watching: while ((wk = watchService.take()) != null) { +// for (WatchEvent event : wk.pollEvents()) { +// final Path changed = (Path) event.context(); +// if (log4jConfigurationPath.equals(parentDir.resolve(changed))) { +// if (isInternalDebugEnabled()) +// debug(log4jConfigurationPath + " has changed, reloading."); +//// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL()); +// } +// } +// // reset the key +// boolean valid = wk.reset(); +// if (!valid) { +// break watching; +// } +// } +// } catch (IOException | InterruptedException e) { +// stdErr("Log4j configuration watcher failed: " + e.getMessage()); +// } // } - - } - - private class Log4jConfWatcherThread extends Thread { - private Path log4jConfigurationPath; - - public Log4jConfWatcherThread(Path log4jConfigurationPath) { - super("Log4j Configuration Watcher"); - try { - this.log4jConfigurationPath = log4jConfigurationPath.toRealPath(); - } catch (IOException e) { - this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath(); - stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage()); - } - } - - public void run() { - Path parentDir = log4jConfigurationPath.getParent(); - try (final WatchService watchService = FileSystems.getDefault().newWatchService()) { - parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); - WatchKey wk; - watching: while ((wk = watchService.take()) != null) { - for (WatchEvent event : wk.pollEvents()) { - final Path changed = (Path) event.context(); - if (log4jConfigurationPath.equals(parentDir.resolve(changed))) { - if (isInternalDebugEnabled()) - debug(log4jConfigurationPath + " has changed, reloading."); -// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL()); - } - } - // reset the key - boolean valid = wk.reset(); - if (!valid) { - break watching; - } - } - } catch (IOException | InterruptedException e) { - stdErr("Log4j configuration watcher failed: " + e.getMessage()); - } - } - } +// } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java deleted file mode 100644 index deb330475..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java +++ /dev/null @@ -1,377 +0,0 @@ -package org.argeo.cms.internal.osgi; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.naming.InvalidNameException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttributes; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.runtime.InitUtils; -import org.argeo.cms.internal.runtime.KernelConstants; -import org.argeo.cms.internal.runtime.KernelUtils; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.util.naming.AttributesDictionary; -import org.argeo.util.naming.LdifParser; -import org.argeo.util.naming.LdifWriter; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.cm.Configuration; -import org.osgi.service.cm.ConfigurationAdmin; -import org.osgi.service.cm.ConfigurationEvent; -import org.osgi.service.cm.ConfigurationListener; - -/** Manages the LDIF-based deployment configuration. */ -public class DeployConfig implements ConfigurationListener { - private final CmsLog log = CmsLog.getLog(getClass()); -// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH); - private SortedMap 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> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); - if (userDirectoryConfigs.size() != 0) { - List activeCns = new ArrayList<>(); - for (int i = 0; i < userDirectoryConfigs.size(); i++) { - Dictionary 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 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 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 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 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 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 index a8c901644..9de7a4fd1 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java @@ -3,7 +3,7 @@ package org.argeo.cms.internal.osgi; /** * Workaround for killing Gogo shell by system shutdown. * - * @see https://issues.apache.org/jira/browse/FELIX-4208 + * @see "https://issues.apache.org/jira/browse/FELIX-4208" */ class GogoShellKiller extends Thread { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java deleted file mode 100644 index 626a057c0..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java +++ /dev/null @@ -1,403 +0,0 @@ -package org.argeo.cms.internal.osgi; - -import java.io.IOException; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import javax.naming.ldap.LdapName; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.kerberos.KerberosPrincipal; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.commons.httpclient.auth.AuthPolicy; -import org.apache.commons.httpclient.auth.CredentialsProvider; -import org.apache.commons.httpclient.params.DefaultHttpParams; -import org.apache.commons.httpclient.params.HttpMethodParams; -import org.apache.commons.httpclient.params.HttpParams; -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.http.client.HttpCredentialProvider; -import org.argeo.cms.internal.http.client.SpnegoAuthScheme; -import org.argeo.cms.internal.runtime.KernelConstants; -import org.argeo.cms.internal.runtime.KernelUtils; -import org.argeo.osgi.transaction.WorkControl; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.osgi.useradmin.AbstractUserDirectory; -import org.argeo.osgi.useradmin.AggregatingUserAdmin; -import org.argeo.osgi.useradmin.LdapUserAdmin; -import org.argeo.osgi.useradmin.LdifUserAdmin; -import org.argeo.osgi.useradmin.OsUserDirectory; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.naming.DnsBrowser; -import org.ietf.jgss.GSSCredential; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedServiceFactory; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -/** - * Aggregates multiple {@link UserDirectory} and integrates them with system - * roles. - */ -public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants { - private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class); - - // OSGi - private Map pidToBaseDn = new HashMap<>(); -// private Map> pidToServiceRegs = new HashMap<>(); -// private ServiceRegistration userAdminReg; - - // JTA -// private final ServiceTracker 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 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 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 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 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 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 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 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 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 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() { - - @Override - public GSSCredential run() throws GSSException { - return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID, - GSSCredential.ACCEPT_ONLY); - - } - - }); - if (log.isDebugEnabled()) - log.debug("GSS acceptor configured for " + krb5Principal); - return serverCredentials; - } catch (Exception gsse) { - throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse); - } - } - - public GSSCredential getAcceptorCredentials() { - return acceptorCredentials; - } - - public boolean hasAcceptorCredentials() { - return acceptorCredentials != null; - } - - public boolean isSingleUser() { - return singleUser; - } - - public void setTransactionManager(WorkControl transactionManager) { - this.transactionManager = transactionManager; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - - /* - * STATIC - */ - - public final static Oid KERBEROS_OID; - static { - try { - KERBEROS_OID = new Oid("1.3.6.1.5.5.2"); - } catch (GSSException e) { - throw new IllegalStateException("Cannot create Kerberos OID", e); - } - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java new file mode 100644 index 000000000..c80933a55 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java @@ -0,0 +1,149 @@ +package org.argeo.cms.internal.runtime; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ForkJoinPool; +import java.util.function.Consumer; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentNotFoundException; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.DName; +import org.argeo.api.acr.RuntimeNamespaceContext; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.dav.DavDepth; +import org.argeo.cms.dav.DavHttpHandler; +import org.argeo.cms.dav.DavPropfind; +import org.argeo.cms.dav.DavResponse; +import org.argeo.cms.http.HttpStatus; +import org.argeo.cms.internal.http.RemoteAuthHttpExchange; +import org.argeo.cms.util.StreamUtils; + +import com.sun.net.httpserver.HttpExchange; + +/** A partial WebDav implementation based on ACR. */ +public class CmsAcrHttpHandler extends DavHttpHandler { + private ProvidedRepository contentRepository; + + @Override + protected NamespaceContext getNamespaceContext(HttpExchange httpExchange, String path) { + // TODO be smarter? + return RuntimeNamespaceContext.getNamespaceContext(); + } + + @Override + protected void handleGET(HttpExchange exchange, String path) throws IOException { + ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(), + new RemoteAuthHttpExchange(exchange)); + if (!session.exists(path)) // not found + throw new ContentNotFoundException(session, path); + Content content = session.get(path); + Optional size = content.get(DName.getcontentlength, Long.class); + try (InputStream in = content.open(InputStream.class)) { + exchange.sendResponseHeaders(HttpStatus.OK.getCode(), size.orElse(0l)); + StreamUtils.copy(in, exchange.getResponseBody()); + } catch (IOException e) { + throw new RuntimeException("Cannot process " + path, e); + } + } + + @Override + protected CompletableFuture handlePROPFIND(HttpExchange exchange, String path, DavPropfind davPropfind, + Consumer consumer) throws IOException { + ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(), + new RemoteAuthHttpExchange(exchange)); + if (!session.exists(path)) // not found + throw new ContentNotFoundException(session, path); + Content content = session.get(path); + + CompletableFuture published = new CompletableFuture(); + ForkJoinPool.commonPool().execute(() -> { + publishDavResponses(content, davPropfind, consumer); + published.complete(null); + }); + return published; + } + + protected void publishDavResponses(Content content, DavPropfind davPropfind, Consumer consumer) { + publishDavResponse(content, davPropfind, consumer, 0); + } + + protected void publishDavResponse(Content content, DavPropfind davPropfind, Consumer consumer, + int currentDepth) { + DavResponse davResponse = new DavResponse(); + String href = CmsConstants.PATH_API_ACR + content.getPath(); + davResponse.setHref(href); + if (content.hasContentClass(DName.collection)) + davResponse.setCollection(true); + if (davPropfind.isAllprop()) { + for (Map.Entry entry : content.entrySet()) { + davResponse.getPropertyNames(HttpStatus.OK).add(entry.getKey()); + processMapEntry(davResponse, entry.getKey(), entry.getValue()); + } + davResponse.getResourceTypes().addAll(content.getContentClasses()); + } else if (davPropfind.isPropname()) { + for (QName key : content.keySet()) { + davResponse.getPropertyNames(HttpStatus.OK).add(key); + } + } else { + for (QName key : davPropfind.getProps()) { + if (content.containsKey(key)) { + davResponse.getPropertyNames(HttpStatus.OK).add(key); + Object value = content.get(key); + processMapEntry(davResponse, key, value); + } else { + davResponse.getPropertyNames(HttpStatus.NOT_FOUND).add(key); + } + if (DName.resourcetype.qName().equals(key)) { + davResponse.getResourceTypes().addAll(content.getContentClasses()); + } + } + + } + + consumer.accept(davResponse); + + // recurse only on collections + if (content.hasContentClass(DName.collection)) { + if (davPropfind.getDepth() == DavDepth.DEPTH_INFINITY + || (davPropfind.getDepth() == DavDepth.DEPTH_1 && currentDepth == 0)) { + for (Content child : content) { + publishDavResponse(child, davPropfind, consumer, currentDepth + 1); + } + } + } + } + + protected void processMapEntry(DavResponse davResponse, QName key, Object value) { + // ignore content classes + if (DName.resourcetype.qName().equals(key)) + return; + String str; + if (value instanceof Collection) { + StringJoiner sj = new StringJoiner("\n"); + for (Object v : (Collection) value) { + sj.add(v.toString()); + } + str = sj.toString(); + } else { + str = value.toString(); + } + davResponse.getProperties().put(key, str); + + } + + public void setContentRepository(ProvidedRepository contentRepository) { + this.contentRepository = contentRepository; + } + +} 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 index 8e29f6673..bd54b2059 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java @@ -1,33 +1,42 @@ package org.argeo.cms.internal.runtime; -import static java.util.Locale.ENGLISH; - import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import org.argeo.api.cms.CmsConstants; +import javax.security.auth.Subject; + import org.argeo.api.cms.CmsContext; import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsEventBus; import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.CmsSessionId; import org.argeo.api.cms.CmsState; -import org.argeo.cms.LocaleUtils; -import org.argeo.cms.internal.osgi.NodeUserAdmin; +import org.argeo.api.uuid.UuidFactory; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.internal.auth.CmsSessionImpl; import org.ietf.jgss.GSSCredential; import org.osgi.service.useradmin.UserAdmin; +/** Reference implementation of {@link CmsContext}. */ public class CmsContextImpl implements CmsContext { + private final CmsLog log = CmsLog.getLog(getClass()); -// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); -// private EgoRepository egoRepository; private static CompletableFuture instance = new CompletableFuture(); private CmsState cmsState; private CmsDeployment cmsDeployment; private UserAdmin userAdmin; + private UuidFactory uuidFactory; + private CmsEventBus cmsEventBus; // i18n private Locale defaultLocale; @@ -35,38 +44,25 @@ public class CmsContextImpl implements CmsContext { private Long availableSince; -// public CmsContextImpl() { -// initTrackers(); -// } + // CMS sessions + private Map cmsSessionsByUuid = new HashMap<>(); + private Map cmsSessionsByLocalId = new HashMap<>(); public void start() { - Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE); - defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString()) - : new Locale(ENGLISH.getLanguage()); - locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES)); - // node repository -// new ServiceTracker(bc, Repository.class, null) { -// @Override -// public Repository addingService(ServiceReference 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 reference, Repository service) { -// super.removedService(reference, service); -//// egoRepository = null; -// } -// -// }.open(); - - checkReadiness(); + List codes = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.LOCALE); + locales = getLocaleList(codes); + if (locales.size() == 0) + throw new IllegalStateException("At least one locale must be set"); + defaultLocale = locales.get(0); + new Thread(() -> { + while (!checkReadiness()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + }, "Check readiness").start(); setInstance(this); } @@ -78,10 +74,13 @@ public class CmsContextImpl implements CmsContext { * Checks whether the deployment is available according to expectations, and * mark it as available. */ - private void checkReadiness() { + private boolean checkReadiness() { if (isAvailable()) - return; - if (cmsDeployment != null && userAdmin != null) { + return true; + if (cmsDeployment == null) + return false; + + if (((CmsDeploymentImpl) cmsDeployment).allExpectedServicesAvailable() && userAdmin != null) { String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA); String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA); availableSince = System.currentTimeMillis(); @@ -98,8 +97,11 @@ public class CmsContextImpl implements CmsContext { if (log.isTraceEnabled()) log.trace("Kernel initialization took " + initDuration + "ms"); tributeToFreeSoftware(initDuration); + + return true; } else { - throw new IllegalStateException("Deployment is not available"); + return false; + // throw new IllegalStateException("Deployment is not available"); } } @@ -129,6 +131,29 @@ public class CmsContextImpl implements CmsContext { throw new UnsupportedOperationException(); } + /** Returns null if argument is null. */ + private static List getLocaleList(List codes) { + if (codes == null) + return null; + ArrayList availableLocales = new ArrayList(); + for (String code : codes) { + if (code == null) + continue; + // variant not supported + int indexUnd = code.indexOf("_"); + Locale locale; + if (indexUnd > 0) { + String language = code.substring(0, indexUnd); + String country = code.substring(indexUnd + 1); + locale = new Locale(language, country); + } else { + locale = new Locale(code); + } + availableLocales.add(locale); + } + return availableLocales; + } + public void setCmsDeployment(CmsDeployment cmsDeployment) { this.cmsDeployment = cmsDeployment; } @@ -141,56 +166,78 @@ public class CmsContextImpl implements CmsContext { this.userAdmin = userAdmin; } + public UuidFactory getUuidFactory() { + return uuidFactory; + } + + public void setUuidFactory(UuidFactory uuidFactory) { + this.uuidFactory = uuidFactory; + } + @Override public Locale getDefaultLocale() { return defaultLocale; } + @Override + public UUID timeUUID() { + return uuidFactory.timeUUID(); + } + @Override public List getLocales() { return locales; } @Override - public synchronized Long getAvailableSince() { + public Long getAvailableSince() { return availableSince; } - public synchronized boolean isAvailable() { + public boolean isAvailable() { return availableSince != null; } + public CmsState getCmsState() { + return cmsState; + } + + @Override + public CmsEventBus getCmsEventBus() { + return cmsEventBus; + } + + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; + } + /* * STATIC */ - public synchronized static CmsContext getCmsContext() { + public static CmsContextImpl getCmsContext() { return getInstance(); } - /** Required by USER login module. */ - public synchronized static UserAdmin getUserAdmin() { - return getInstance().userAdmin; - } - /** Required by SPNEGO login module. */ - @Deprecated - public synchronized static GSSCredential getAcceptorCredentials() { - // FIXME find a cleaner way - return ((NodeUserAdmin) getInstance().userAdmin).getAcceptorCredentials(); + public GSSCredential getAcceptorCredentials() { + // TODO find a cleaner way + return ((CmsUserAdmin) userAdmin).getAcceptorCredentials(); } - private synchronized static void setInstance(CmsContextImpl cmsContextImpl) { + private static void setInstance(CmsContextImpl cmsContextImpl) { if (cmsContextImpl != null) { if (instance.isDone()) throw new IllegalStateException("CMS Context is already set"); instance.complete(cmsContextImpl); } else { + if (!instance.isDone()) + instance.cancel(true); instance = new CompletableFuture(); } } - private synchronized static CmsContextImpl getInstance() { + private static CmsContextImpl getInstance() { try { return instance.get(); } catch (InterruptedException | ExecutionException e) { @@ -198,4 +245,53 @@ public class CmsContextImpl implements CmsContext { } } + public UserAdmin getUserAdmin() { + return userAdmin; + } + + /* + * CMS Sessions + */ + + @Override + public CmsSession getCmsSession(Subject subject) { + if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty()) + return null; + CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next(); + return getCmsSessionByUuid(cmsSessionId.getUuid()); + } + + public void registerCmsSession(CmsSessionImpl cmsSession) { + if (cmsSessionsByUuid.containsKey(cmsSession.getUuid()) + || cmsSessionsByLocalId.containsKey(cmsSession.getLocalId())) + throw new IllegalStateException("CMS session " + cmsSession + " is already registered."); + cmsSessionsByUuid.put(cmsSession.getUuid(), cmsSession); + cmsSessionsByLocalId.put(cmsSession.getLocalId(), cmsSession); + } + + public void unregisterCmsSession(CmsSessionImpl cmsSession) { + if (!cmsSessionsByUuid.containsKey(cmsSession.getUuid()) + || !cmsSessionsByLocalId.containsKey(cmsSession.getLocalId())) + throw new IllegalStateException("CMS session " + cmsSession + " is not registered."); + CmsSession removed = cmsSessionsByUuid.remove(cmsSession.getUuid()); + assert removed == cmsSession; + cmsSessionsByLocalId.remove(cmsSession.getLocalId()); + } + + /** + * The {@link CmsSession} related to this UUID, or null if not + * registered. + */ + public CmsSessionImpl getCmsSessionByUuid(UUID uuid) { + return cmsSessionsByUuid.get(uuid); + } + + /** + * The {@link CmsSession} related to this local id, or null if not + * registered. + */ + public CmsSessionImpl getCmsSessionByLocalId(String localId) { + return cmsSessionsByLocalId.get(localId); + } + } 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 index 4ffa03a63..e2d1fb97a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java @@ -1,207 +1,115 @@ package org.argeo.cms.internal.runtime; -import java.io.IOException; -import java.net.URL; -import java.util.Dictionary; +import static org.argeo.api.cms.CmsConstants.CONTEXT_PATH; +import java.util.Map; +import java.util.TreeMap; + +import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsDeployment; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsState; -import org.argeo.cms.internal.osgi.DeployConfig; -import org.osgi.service.http.HttpService; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.CmsSshd; +import org.argeo.cms.internal.http.CmsAuthenticator; +import org.argeo.cms.internal.http.PublicCmsAuthenticator; + +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; -/** Implementation of a CMS deployment. */ +/** Reference implementation of {@link CmsDeployment}. */ public class CmsDeploymentImpl implements CmsDeployment { private final CmsLog log = CmsLog.getLog(getClass()); -// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); -// private Long availableSince; + private CmsState cmsState; - // Readiness -// private boolean nodeAvailable = false; -// private boolean userAdminAvailable = false; + // Expectations private boolean httpExpected = false; -// private boolean httpAvailable = false; - private HttpService httpService; + private boolean sshdExpected = false; - private CmsState cmsState; - private DeployConfig deployConfig; - - public CmsDeploymentImpl() { -// ServiceReference nodeStateSr = bc.getServiceReference(NodeState.class); -// if (nodeStateSr == null) -// throw new CmsException("No node state available"); + // HTTP + private HttpServer httpServer; + private Map httpHandlers = new TreeMap<>(); + private Map httpAuthenticators = new TreeMap<>(); -// NodeState nodeState = bc.getService(nodeStateSr); -// cleanState = nodeState.isClean(); + // SSHD + private CmsSshd cmsSshd; -// nodeHttp = new NodeHttp(); - initTrackers(); + public void start() { + log.debug(() -> "CMS deployment available"); } - private void initTrackers() { -// ServiceTracker httpSt = new ServiceTracker(bc, HttpService.class, null) { -// -// @Override -// public HttpService addingService(ServiceReference 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(bc, UserAdmin.class, null) { -// @Override -// public UserAdmin addingService(ServiceReference reference) { -// UserAdmin userAdmin = super.addingService(reference); -// addStandardSystemRoles(userAdmin); -// userAdminAvailable = true; -// checkReadiness(); -// return userAdmin; -// } -// }; -// // userAdminSt.open(); -// KernelUtils.asyncOpen(userAdminSt); - -// ServiceTracker confAdminSt = new ServiceTracker(bc, -// ConfigurationAdmin.class, null) { -// @Override -// public ConfigurationAdmin addingService(ServiceReference reference) { -// ConfigurationAdmin configurationAdmin = bc.getService(reference); -//// boolean isClean; -//// try { -//// Configuration[] confs = configurationAdmin -//// .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")"); -//// isClean = confs == null || confs.length == 0; -//// } catch (Exception e) { -//// throw new IllegalStateException("Cannot analyse clean state", e); -//// } -// deployConfig = new DeployConfig(configurationAdmin, isClean); -// Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null); -//// JcrInitUtils.addToDeployment(CmsDeployment.this); -// httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; -// try { -// Configuration[] configs = configurationAdmin -// .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")"); -// -// boolean hasDomain = false; -// for (Configuration config : configs) { -// Object realm = config.getProperties().get(UserAdminConf.realm.name()); -// if (realm != null) { -// log.debug("Found realm: " + realm); -// hasDomain = true; -// } -// } -// if (hasDomain) { -// loadIpaJaasConfiguration(); -// } -// } catch (Exception e) { -// throw new IllegalStateException("Cannot initialize config", e); -// } -// return super.addingService(reference); -// } -// }; -// // confAdminSt.open(); -// KernelUtils.asyncOpen(confAdminSt); + public void stop() { } - public void start() { - httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; - if (deployConfig.hasDomain()) { - loadIpaJaasConfiguration(); - } - -// while (!isHttpAvailableOrNotExpected()) { -// try { -// Thread.sleep(100); -// } catch (InterruptedException e) { -// log.error("Interrupted while waiting for http"); -// } -// } - } + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; - public void addFactoryDeployConfig(String factoryPid, Dictionary props) { - deployConfig.putFactoryDeployConfig(factoryPid, props); - deployConfig.save(); - try { - deployConfig.loadConfigs(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } + String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty()); + String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty()); + httpExpected = httpPort != null || httpsPort != null; - public Dictionary getProps(String factoryPid, String cn) { - return deployConfig.getProps(factoryPid, cn); + String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty()); + sshdExpected = sshdPort != null; } -// private void addStandardSystemRoles(UserAdmin userAdmin) { -// // we assume UserTransaction is already available (TODO make it more robust) -// WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class)); -// try { -// userTransaction.begin(); -// Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN); -// if (adminRole == null) { -// adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP); -// } -// if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) { -// Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP); -// userAdminRole.addMember(adminRole); -// } -// userTransaction.commit(); -// } catch (Exception e) { -// try { -// userTransaction.rollback(); -// } catch (Exception e1) { -// // silent -// } -// throw new IllegalStateException("Cannot add standard system roles", e); -// } -// } - - public boolean isHttpAvailableOrNotExpected() { - return (httpExpected ? httpService != null : true); + public void setHttpServer(HttpServer httpServer) { + this.httpServer = httpServer; + // create contexts whose handles had already been published + for (String contextPath : httpHandlers.keySet()) { + HttpHandler httpHandler = httpHandlers.get(contextPath); + CmsAuthenticator authenticator = httpAuthenticators.get(contextPath); + createHttpContext(contextPath, httpHandler, authenticator); + } } - private void loadIpaJaasConfiguration() { - if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) { - String jaasConfig = KernelConstants.JAAS_CONFIG_IPA; - URL url = getClass().getClassLoader().getResource(jaasConfig); - KernelUtils.setJaasConfiguration(url); - log.debug("Set IPA JAAS configuration."); + public void addHttpHandler(HttpHandler httpHandler, Map properties) { + final String contextPath = properties.get(CONTEXT_PATH); + if (contextPath == null) { + log.warn("Property " + CONTEXT_PATH + " not set on HTTP handler " + properties + ". Ignoring it."); + return; + } + boolean isPublic = Boolean.parseBoolean(properties.get(CmsConstants.CONTEXT_PUBLIC)); + CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator(); + httpHandlers.put(contextPath, httpHandler); + httpAuthenticators.put(contextPath, authenticator); + if (httpServer == null) { + return; + } else { + createHttpContext(contextPath, httpHandler, authenticator); } } - public void stop() { -// if (nodeHttp != null) -// nodeHttp.destroy(); - -// try { -// JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER); -// } catch (Exception e) { -// log.error("Cannot stop default Jetty server.", e); -// } - - if (deployConfig != null) { - deployConfig.save(); - // new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start(); - } + public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) { + HttpContext httpContext = httpServer.createContext(contextPath); + // we want to set the authenticator BEFORE the handler actually becomes active + httpContext.setAuthenticator(authenticator); + httpContext.setHandler(httpHandler); + log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName()); } - public void setDeployConfig(DeployConfig deployConfig) { - this.deployConfig = deployConfig; + public void removeHttpHandler(HttpHandler httpHandler, Map properties) { + final String contextPath = properties.get(CmsConstants.CONTEXT_PATH); + if (contextPath == null) + return; // ignore silently + httpHandlers.remove(contextPath); + if (httpServer == null) + return; + httpServer.removeContext(contextPath); + log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName()); } - public void setCmsState(CmsState cmsState) { - this.cmsState = cmsState; + public boolean allExpectedServicesAvailable() { + if (httpExpected && httpServer == null) + return false; + if (sshdExpected && cmsSshd == null) + return false; + return true; } - public void setHttpService(HttpService httpService) { - this.httpService = httpService; + public void setCmsSshd(CmsSshd cmsSshd) { + this.cmsSshd = cmsSshd; } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java new file mode 100644 index 000000000..99f6c1d8d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java @@ -0,0 +1,101 @@ +package org.argeo.cms.internal.runtime; + +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.Flow; +import java.util.concurrent.Flow.Subscription; +import java.util.concurrent.SubmissionPublisher; + +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsEventSubscriber; +import org.argeo.api.cms.CmsLog; + +/** {@link CmsEventBus} implementation based on {@link Flow}. */ +public class CmsEventBusImpl implements CmsEventBus { + private final CmsLog log = CmsLog.getLog(CmsEventBus.class); + + private Map>> topics = new TreeMap<>(); + + @Override + public void sendEvent(String topic, Map event) { + SubmissionPublisher> publisher = topics.get(topic); + if (publisher == null) + return; // no one is interested + publisher.submit(event); + } + + @Override + public void addEventSubscriber(String topic, CmsEventSubscriber subscriber) { + synchronized (topics) { + if (!topics.containsKey(topic)) + topics.put(topic, new SubmissionPublisher<>()); + } + SubmissionPublisher> publisher = topics.get(topic); + CmsEventFlowSubscriber flowSubscriber = new CmsEventFlowSubscriber(topic, subscriber); + publisher.subscribe(flowSubscriber); + } + + @Override + public void removeEventSubscriber(String topic, CmsEventSubscriber subscriber) { + SubmissionPublisher> publisher = topics.get(topic); + if (publisher == null) { + log.error("There should be an event topic " + topic); + return; + } + for (Flow.Subscriber> flowSubscriber : publisher.getSubscribers()) { + if (flowSubscriber instanceof CmsEventFlowSubscriber) + ((CmsEventFlowSubscriber) flowSubscriber).unsubscribe(); + } + synchronized (topics) { + if (!publisher.hasSubscribers()) { + publisher.close(); + topics.remove(topic); + } + } + } + + /** A subscriber to a topic. */ + static class CmsEventFlowSubscriber implements Flow.Subscriber> { + private String topic; + private CmsEventSubscriber eventSubscriber; + + private Subscription subscription; + + public CmsEventFlowSubscriber(String topic, CmsEventSubscriber eventSubscriber) { + this.topic = topic; + this.eventSubscriber = eventSubscriber; + } + + @Override + public void onSubscribe(Subscription subscription) { + this.subscription = subscription; + this.subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Map item) { + eventSubscriber.onEvent(topic, item); + } + + @Override + public void onError(Throwable throwable) { + // TODO Auto-generated method stub + + } + + @Override + public void onComplete() { + // TODO Auto-generated method stub + + } + + void unsubscribe() { + if (subscription != null) + subscription.cancel(); + else + throw new IllegalStateException("No subscription to cancel"); + } + + } + +} 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 index a0c4b0c0b..5c3838a0a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java @@ -1,15 +1,44 @@ package org.argeo.cms.internal.runtime; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.Reader; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.security.auth.login.Configuration; +import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsState; +import org.argeo.api.uuid.UuidFactory; +import org.argeo.cms.CmsDeployProperty; import org.argeo.cms.auth.ident.IdentClient; -import org.osgi.framework.Constants; +import org.argeo.cms.util.FsUtils; /** * Implementation of a {@link CmsState}, initialising the required services. @@ -20,10 +49,47 @@ public class CmsStateImpl implements CmsState { // REFERENCES private Long availableSince; - private String stateUuid; + private UUID uuid; // private final boolean cleanState; private String hostname; + private UuidFactory uuidFactory; + + private final Map deployPropertyDefaults; + + public CmsStateImpl() { + this.deployPropertyDefaults = Collections.unmodifiableMap(createDeployPropertiesDefaults()); + } + + protected Map createDeployPropertiesDefaults() { + Map deployPropertyDefaults = new HashMap<>(); + deployPropertyDefaults.put(CmsDeployProperty.NODE_INIT, "../../init"); + deployPropertyDefaults.put(CmsDeployProperty.LOCALE, Locale.getDefault().toString()); + + // certificates + deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORETYPE, KernelConstants.PKCS12); + deployPropertyDefaults.put(CmsDeployProperty.SSL_PASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD); + Path keyStorePath = getDataPath(KernelConstants.DEFAULT_KEYSTORE_PATH); + if (keyStorePath != null) { + deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORE, keyStorePath.toAbsolutePath().toString()); + } + + Path trustStorePath = getDataPath(KernelConstants.DEFAULT_TRUSTSTORE_PATH); + if (trustStorePath != null) { + deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORE, trustStorePath.toAbsolutePath().toString()); + } + deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORETYPE, KernelConstants.PKCS12); + deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD); + + // SSH + Path authorizedKeysPath = getDataPath(KernelConstants.NODE_SSHD_AUTHORIZED_KEYS_PATH); + if (authorizedKeysPath != null) { + deployPropertyDefaults.put(CmsDeployProperty.SSHD_AUTHORIZEDKEYS, + authorizedKeysPath.toAbsolutePath().toString()); + } + return deployPropertyDefaults; + } + public void start() { // Runtime.getRuntime().addShutdownHook(new CmsShutdown()); @@ -34,30 +100,85 @@ public class CmsStateImpl implements CmsState { if (log.isTraceEnabled()) log.trace("CMS State started"); - this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID); +// String stateUuidStr = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID); +// this.uuid = UUID.fromString(stateUuidStr); + this.uuid = uuidFactory.timeUUID(); // this.cleanState = stateUuid.equals(frameworkUuid); - try { - this.hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - log.error("Cannot set hostname: " + e); + + // hostname + this.hostname = getDeployProperty(CmsDeployProperty.HOST); + // TODO verify we have access to the IP address + if (hostname == null) { + final String LOCALHOST_IP = "::1"; + ForkJoinTask hostnameFJT = ForkJoinPool.commonPool().submit(() -> { + try { + String hostname = InetAddress.getLocalHost().getHostName(); + return hostname; + } catch (UnknownHostException e) { + throw new IllegalStateException("Cannot get local hostname", e); + } + }); + try { + this.hostname = hostnameFJT.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + this.hostname = LOCALHOST_IP; + log.warn("Could not get local hostname, using " + this.hostname); + } } availableSince = System.currentTimeMillis(); - if (log.isDebugEnabled()) + if (log.isDebugEnabled()) { // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? " // (clean state) " : " ")); - log.debug("## CMS starting... (" + stateUuid + ")"); + StringJoiner sb = new StringJoiner("\n"); + CmsDeployProperty[] deployProperties = CmsDeployProperty.values(); + Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name())); + for (CmsDeployProperty deployProperty : deployProperties) { + List values = getDeployProperties(deployProperty); + for (int i = 0; i < values.size(); i++) { + String value = values.get(i); + if (value != null) { + boolean isDefault = deployPropertyDefaults.containsKey(deployProperty) + && value.equals(deployPropertyDefaults.get(deployProperty)); + String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value + + (isDefault ? " (default)" : ""); + sb.add(line); + } + } + } + log.debug("## CMS starting... (" + uuid + ")\n" + sb + "\n"); + } -// initI18n(); -// initServices(); + Path privateBase = getDataPath(KernelConstants.DIR_PRIVATE); + if (privateBase != null && !Files.exists(privateBase)) {// first init + firstInit(); + Files.createDirectories(privateBase); + } - } catch (RuntimeException e) { - log.error("## FATAL: CMS activator failed", e); + } catch (RuntimeException | IOException e) { + log.error("## FATAL: CMS state failed", e); } } private void initSecurity() { - if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) { + // private directory permissions + Path privateDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_PRIVATE); + if (privateDir != null) { + // TODO rather check whether we can read and write + Set posixPermissions = new HashSet<>(); + posixPermissions.add(PosixFilePermission.OWNER_READ); + posixPermissions.add(PosixFilePermission.OWNER_WRITE); + posixPermissions.add(PosixFilePermission.OWNER_EXECUTE); + try { + if (!Files.exists(privateDir)) + Files.createDirectories(privateDir); + Files.setPosixFilePermissions(privateDir, posixPermissions); + } catch (IOException e) { + log.error("Cannot set permissions on " + privateDir, e); + } + } + + if (getDeployProperty(CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) { String jaasConfig = KernelConstants.JAAS_CONFIG; URL url = getClass().getResource(jaasConfig); // System.setProperty(KernelConstants.JAAS_CONFIG_PROP, @@ -66,16 +187,193 @@ public class CmsStateImpl implements CmsState { } // explicitly load JAAS configuration Configuration.getConfiguration(); + + boolean initSsl = getDeployProperty(CmsDeployProperty.HTTPS_PORT) != null; + if (initSsl) { + initCertificates(); + } + } + + private void initCertificates() { + // server certificate + Path keyStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE)); + Path pemKeyPath = getDataPath(KernelConstants.DEFAULT_PEM_KEY_PATH); + Path pemCertPath = getDataPath(KernelConstants.DEFAULT_PEM_CERT_PATH); + char[] keyStorePassword = getDeployProperty(CmsDeployProperty.SSL_PASSWORD).toCharArray(); + + // Keystore + // if PEM files both exists, update the PKCS12 file + if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) { + // TODO check certificate update time? monitor changes? + KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, + getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE)); + try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII); + BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(pemCertPath));) { + PkiUtils.loadPrivateCertificatePem(keyStore, CmsConstants.NODE, key, keyStorePassword, cert); + Files.createDirectories(keyStorePath.getParent()); + PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore); + if (log.isDebugEnabled()) + log.debug("PEM certificate stored in " + keyStorePath); + } catch (IOException e) { + log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e); + } + } + + // Truststore + Path trustStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE)); + char[] trustStorePassword = getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD).toCharArray(); + + // IPA CA + Path ipaCaCertPath = Paths.get(KernelConstants.IPA_PEM_CA_CERT_PATH); + if (Files.exists(ipaCaCertPath)) { + KeyStore trustStore = PkiUtils.getKeyStore(trustStorePath, trustStorePassword, + getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE)); + try (BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(ipaCaCertPath));) { + PkiUtils.loadTrustedCertificatePem(trustStore, trustStorePassword, cert); + Files.createDirectories(keyStorePath.getParent()); + PkiUtils.saveKeyStore(trustStorePath, trustStorePassword, trustStore); + if (log.isDebugEnabled()) + log.debug("IPA CA certificate stored in " + trustStorePath); + } catch (IOException e) { + log.error("Cannot trust CA certificate", e); + } + } + +// if (!Files.exists(keyStorePath)) +// PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12); } public void stop() { if (log.isDebugEnabled()) - log.debug("CMS stopping... (" + this.stateUuid + ")"); + log.debug("CMS stopping... (" + this.uuid + ")"); long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60; log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##"); } + private void firstInit() throws IOException { + log.info("## FIRST INIT ##"); + List nodeInits = getDeployProperties(CmsDeployProperty.NODE_INIT); +// if (nodeInits == null) +// nodeInits = "../../init"; + CmsStateImpl.prepareFirstInitInstanceArea(nodeInits); + } + + @Override + public String getDeployProperty(String property) { + CmsDeployProperty deployProperty = CmsDeployProperty.find(property); + if (deployProperty == null) { + // legacy + if (property.startsWith("argeo.node.")) { + return doGetDeployProperty(property); + } + if (property.equals("argeo.i18n.locales")) { + String value = doGetDeployProperty(property); + if (value != null) { + log.warn("Property " + property + " was ignored (value=" + value + ")"); + + } + return null; + } + throw new IllegalArgumentException("Unsupported deploy property " + property); + } + int index = CmsDeployProperty.getPropertyIndex(property); + return getDeployProperty(deployProperty, index); + } + + @Override + public List getDeployProperties(String property) { + CmsDeployProperty deployProperty = CmsDeployProperty.find(property); + if (deployProperty == null) + return new ArrayList<>(); + return getDeployProperties(deployProperty); + } + + public static List getDeployProperties(CmsState cmsState, CmsDeployProperty deployProperty) { + return ((CmsStateImpl) cmsState).getDeployProperties(deployProperty); + } + + public List getDeployProperties(CmsDeployProperty deployProperty) { + List res = new ArrayList<>(deployProperty.getMaxCount()); + for (int i = 0; i < deployProperty.getMaxCount(); i++) { + // String propertyName = i == 0 ? deployProperty.getProperty() : + // deployProperty.getProperty() + "." + i; + String value = getDeployProperty(deployProperty, i); + res.add(i, value); + } + return res; + } + + public static String getDeployProperty(CmsState cmsState, CmsDeployProperty deployProperty) { + return ((CmsStateImpl) cmsState).getDeployProperty(deployProperty); + } + + public String getDeployProperty(CmsDeployProperty deployProperty) { + String value = getDeployProperty(deployProperty, 0); + return value; + } + + public String getDeployProperty(CmsDeployProperty deployProperty, int index) { + String propertyName = deployProperty.getProperty() + (index == 0 ? "" : "." + index); + String value = doGetDeployProperty(propertyName); + if (value == null && index == 0) { + // try defaults + if (deployPropertyDefaults.containsKey(deployProperty)) { + value = deployPropertyDefaults.get(deployProperty); + if (deployProperty.isSystemPropertyOnly()) + System.setProperty(deployProperty.getProperty(), value); + } + + if (value == null) { + // try legacy properties + String legacyProperty = switch (deployProperty) { + case DIRECTORY -> "argeo.node.useradmin.uris"; + case DB_URL -> "argeo.node.dburl"; + case DB_USER -> "argeo.node.dbuser"; + case DB_PASSWORD -> "argeo.node.dbpassword"; + case HTTP_PORT -> "org.osgi.service.http.port"; + case HTTPS_PORT -> "org.osgi.service.http.port.secure"; + case HOST -> "org.eclipse.equinox.http.jetty.http.host"; + case LOCALE -> "argeo.i18n.defaultLocale"; + + default -> null; + }; + if (legacyProperty != null) { + value = doGetDeployProperty(legacyProperty); + if (value != null) { + log.warn("Retrieved deploy property " + deployProperty.getProperty() + + " through deprecated property " + legacyProperty); + } + } + } + } + if (index == 0 && deployProperty.isSystemPropertyOnly()) { + String systemPropertyValue = System.getProperty(deployProperty.getProperty()); + if (!Objects.equals(value, systemPropertyValue)) + throw new IllegalStateException( + "Property " + deployProperty + " must be a ssystem property, but its value is " + value + + ", while the system property value is " + systemPropertyValue); + } + return value != null ? value.toString() : null; + } + + protected String getLegacyProperty(String legacyProperty, CmsDeployProperty deployProperty) { + String value = doGetDeployProperty(legacyProperty); + if (value != null) { + log.warn("Retrieved deploy property " + deployProperty.getProperty() + " through deprecated property " + + legacyProperty + "."); + } + return value; + } + + protected String doGetDeployProperty(String property) { + return KernelUtils.getFrameworkProp(property); + } + + @Override + public Path getDataPath(String relativePath) { + return KernelUtils.getOsgiInstancePath(relativePath); + } @Override public Long getAvailableSince() { @@ -85,10 +383,50 @@ public class CmsStateImpl implements CmsState { /* * ACCESSORS */ + @Override + public UUID getUuid() { + return uuid; + } + + public void setUuidFactory(UuidFactory uuidFactory) { + this.uuidFactory = uuidFactory; + } + public String getHostname() { return hostname; } + /** + * Called before node initialisation, in order populate OSGi instance are with + * some files (typically LDIF, etc). + */ + public static void prepareFirstInitInstanceArea(List nodeInits) { + + for (String nodeInit : nodeInits) { + if (nodeInit == null) + continue; + + if (nodeInit.startsWith("http")) { + // TODO reconnect it + // registerRemoteInit(nodeInit); + } else { + + // TODO use java.nio.file + Path initDir; + if (nodeInit.startsWith(".")) + initDir = KernelUtils.getExecutionDir(nodeInit); + else + initDir = Paths.get(nodeInit); + // TODO also uncompress archives + if (Files.exists(initDir)) { + Path dataPath = KernelUtils.getOsgiInstancePath(""); + FsUtils.copyDirectory(initDir, dataPath); + log.info("CMS initialized from " + initDir); + } + } + } + } + /* * STATIC */ diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java new file mode 100644 index 000000000..e6f903d39 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java @@ -0,0 +1,414 @@ +package org.argeo.cms.internal.runtime; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.api.cms.directory.UserDirectory; +import org.argeo.api.cms.transaction.WorkControl; +import org.argeo.api.cms.transaction.WorkTransaction; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.dns.DnsBrowser; +import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin; +import org.argeo.cms.osgi.useradmin.DirectoryUserAdmin; +import org.argeo.cms.runtime.DirectoryConf; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; + +/** + * Aggregates multiple {@link UserDirectory} and integrates them with system + * roles. + */ +public class CmsUserAdmin extends AggregatingUserAdmin { + private final static CmsLog log = CmsLog.getLog(CmsUserAdmin.class); + + // GSS API + private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH); + private GSSCredential acceptorCredentials; + + private boolean singleUser = false; + + private WorkControl transactionManager; + private WorkTransaction userTransaction; + + private CmsState cmsState; + + public CmsUserAdmin() { + super(CmsConstants.SYSTEM_ROLES_BASEDN, CmsConstants.TOKENS_BASEDN); + } + + public void start() { + super.start(); + List> configs = getUserDirectoryConfigs(); + for (Dictionary config : configs) { + enableUserDirectory(config); +// if (userDirectory.getRealm().isPresent()) +// loadIpaJaasConfiguration(); + } + log.debug(() -> "CMS user admin available"); + } + + public void stop() { +// for (UserDirectory userDirectory : getUserDirectories()) { +// removeUserDirectory(userDirectory); +// } + super.stop(); + } + + protected List> getUserDirectoryConfigs() { + List> res = new ArrayList<>(); + Path nodeBase = cmsState.getDataPath(KernelConstants.DIR_PRIVATE); + List uris = new ArrayList<>(); + + // node roles + String nodeRolesUri = null;// getFrameworkProp(CmsConstants.ROLES_URI); + String baseNodeRoleDn = CmsConstants.SYSTEM_ROLES_BASEDN; + if (nodeRolesUri == null && nodeBase != null) { + nodeRolesUri = baseNodeRoleDn + ".ldif"; + Path nodeRolesFile = nodeBase.resolve(nodeRolesUri); + if (!Files.exists(nodeRolesFile)) + try { + Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeRoleDn + ".ldif"), nodeRolesFile); + } catch (IOException e) { + throw new RuntimeException("Cannot copy demo resource", e); + } + // nodeRolesUri = nodeRolesFile.toURI().toString(); + } + if (nodeRolesUri != null) + uris.add(nodeRolesUri); + + // node tokens + String nodeTokensUri = null;// getFrameworkProp(CmsConstants.TOKENS_URI); + String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN; + if (nodeTokensUri == null && nodeBase != null) { + nodeTokensUri = baseNodeTokensDn + ".ldif"; + Path nodeTokensFile = nodeBase.resolve(nodeTokensUri); + if (!Files.exists(nodeTokensFile)) + try { + Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeTokensDn + ".ldif"), nodeTokensFile); + } catch (IOException e) { + throw new RuntimeException("Cannot copy demo resource", e); + } + // nodeRolesUri = nodeRolesFile.toURI().toString(); + } + if (nodeTokensUri != null) + uris.add(nodeTokensUri); + + // Business roles +// String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS); + List userAdminUris = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DIRECTORY);// getFrameworkProp(CmsConstants.USERADMIN_URIS); + for (String userAdminUri : userAdminUris) { + if (userAdminUri == null) + continue; +// if (!userAdminUri.trim().equals("")) + uris.add(userAdminUri); + } + + if (uris.size() == 0 && nodeBase != null) { + // TODO put this somewhere else + String demoBaseDn = "dc=example,dc=com"; + String userAdminUri = demoBaseDn + ".ldif"; + Path businessRolesFile = nodeBase.resolve(userAdminUri); + Path systemRolesFile = nodeBase.resolve("ou=roles,ou=node.ldif"); + if (!Files.exists(businessRolesFile)) + try { + Files.copy(CmsUserAdmin.class.getResourceAsStream(demoBaseDn + ".ldif"), businessRolesFile); + if (!Files.exists(systemRolesFile)) + Files.copy(CmsUserAdmin.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), + systemRolesFile); + } catch (IOException e) { + throw new RuntimeException("Cannot copy demo resources", e); + } + // userAdminUris = businessRolesFile.toURI().toString(); + log.warn("## DEV Using dummy base DN " + demoBaseDn); + // TODO downgrade security level + } + + // Interprets URIs + for (String uri : uris) { + URI u; + try { + u = new URI(uri); + if (u.getPath() == null) + throw new IllegalArgumentException( + "URI " + uri + " must have a path in order to determine base DN"); + if (u.getScheme() == null) { + if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../")) + u = Paths.get(uri).toRealPath().toUri(); + else if (!uri.contains("/")) { + // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri); + u = new URI(uri); + } else + throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri"); + } else if (u.getScheme().equals(DirectoryConf.SCHEME_FILE)) { + u = Paths.get(u).toRealPath().toUri(); + } + } catch (Exception e) { + throw new RuntimeException("Cannot interpret " + uri + " as an uri", e); + } + + try { + Dictionary properties = DirectoryConf.uriAsProperties(u.toString()); + res.add(properties); + } catch (Exception e) { + log.error("Cannot load user directory " + u, e); + } + } + + return res; + } + + public UserDirectory enableUserDirectory(Dictionary properties) { + String uri = (String) properties.get(DirectoryConf.uri.name()); + Object realm = properties.get(DirectoryConf.realm.name()); + URI u; + try { + if (uri == null) { + String baseDn = (String) properties.get(DirectoryConf.baseDn.name()); + u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_PRIVATE + '/' + baseDn + ".ldif"); + } else if (realm != null) { + u = null; + } else { + u = new URI(uri); + } + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Badly formatted URI " + uri, e); + } + + // Create + UserDirectory userDirectory = new DirectoryUserAdmin(u, properties); +// if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme()) +// || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) { +// userDirectory = new LdapUserAdmin(properties); +// } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) { +// userDirectory = new LdifUserAdmin(u, properties); +// } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) { +// userDirectory = new OsUserDirectory(u, properties); +// singleUser = true; +// } else { +// throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); +// } + String basePath = userDirectory.getBase(); + + addUserDirectory(userDirectory); + if (isSystemRolesBaseDn(basePath)) { + addStandardSystemRoles(); + } + if (log.isDebugEnabled()) { + log.debug("User directory " + userDirectory.getBase() + (u != null ? " [" + u.getScheme() + "]" : "") + + " enabled." + (realm != null ? " " + realm + " realm." : "")); + } + return userDirectory; + } + + protected void addStandardSystemRoles() { + // we assume UserTransaction is already available (TODO make it more robust) + try { + userTransaction.begin(); + Role adminRole = getRole(CmsConstants.ROLE_ADMIN); + if (adminRole == null) { + adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP); + } + if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) { + Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP); + userAdminRole.addMember(adminRole); + } + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + // silent + } + throw new IllegalStateException("Cannot add standard system roles", e); + } + } + + @Override + protected void addAbstractSystemRoles(Authorization rawAuthorization, Set sysRoles) { + if (rawAuthorization.getName() == null) { + sysRoles.add(CmsConstants.ROLE_ANONYMOUS); + } else { + sysRoles.add(CmsConstants.ROLE_USER); + } + } + + @Override + protected void postAdd(UserDirectory userDirectory) { + userDirectory.setTransactionControl(transactionManager); + + Optional realm = userDirectory.getRealm(); + if (realm.isPresent()) { + loadIpaJaasConfiguration(); + if (Files.exists(nodeKeyTab)) { + String servicePrincipal = getKerberosServicePrincipal(realm.get()); + if (servicePrincipal != null) { + CallbackHandler callbackHandler = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) + if (callback instanceof NameCallback) + ((NameCallback) callback).setName(servicePrincipal); + + } + }; + try { + LoginContext nodeLc = CmsAuth.NODE.newLoginContext(callbackHandler); + nodeLc.login(); + acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal); + } catch (LoginException e) { + throw new IllegalStateException("Cannot log in kernel", e); + } + } + } + + } + } + + @Override + protected void preDestroy(UserDirectory userDirectory) { + Optional realm = userDirectory.getRealm(); + if (realm.isPresent()) { + if (acceptorCredentials != null) { + try { + acceptorCredentials.dispose(); + } catch (GSSException e) { + // silent + } + acceptorCredentials = null; + } + } + } + + private void loadIpaJaasConfiguration() { + if (CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) { + String jaasConfig = KernelConstants.JAAS_CONFIG_IPA; + URL url = getClass().getClassLoader().getResource(jaasConfig); + KernelUtils.setJaasConfiguration(url); + log.debug("Set IPA JAAS configuration."); + } + } + + protected String getKerberosServicePrincipal(String realm) { + if (!Files.exists(nodeKeyTab)) + return null; + List dns = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DNS); + String hostname = CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.HOST); + try (DnsBrowser dnsBrowser = new DnsBrowser(dns)) { + hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName(); + String dnsZone = hostname.substring(hostname.indexOf('.') + 1); + String ipv4fromDns = dnsBrowser.getRecord(hostname, "A"); + String ipv6fromDns = dnsBrowser.getRecord(hostname, "AAAA"); + if (ipv4fromDns == null && ipv6fromDns == null) + throw new IllegalStateException("hostname " + hostname + " is not registered in DNS"); + // boolean consistentIp = localhost.getHostAddress().equals(ipfromDns); + String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT"); + if (kerberosDomain != null && kerberosDomain.equals(realm)) { + return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain; + } else + return null; + } catch (Exception e) { + log.warn("Exception when determining kerberos principal", e); + return null; + } + } + + private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) { + // not static because class is not supported by Android + final Oid KERBEROS_OID; + try { + KERBEROS_OID = new Oid("1.3.6.1.5.5.2"); + } catch (GSSException e) { + throw new IllegalStateException("Cannot create Kerberos OID", e); + } + // GSS + Iterator 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() { + + @Override + public GSSCredential run() throws GSSException { + return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID, + GSSCredential.ACCEPT_ONLY); + + } + + }); + if (log.isDebugEnabled()) + log.debug("GSS acceptor configured for " + krb5Principal); + return serverCredentials; + } catch (Exception gsse) { + throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse); + } + } + + public GSSCredential getAcceptorCredentials() { + return acceptorCredentials; + } + + public boolean hasAcceptorCredentials() { + return acceptorCredentials != null; + } + + public boolean isSingleUser() { + return singleUser; + } + + public void setTransactionManager(WorkControl transactionManager) { + this.transactionManager = transactionManager; + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java new file mode 100644 index 000000000..1eb227b0e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java @@ -0,0 +1,755 @@ +package org.argeo.cms.internal.runtime; + +import static org.argeo.api.acr.ldap.LdapAttr.cn; +import static org.argeo.api.acr.ldap.LdapAttr.description; +import static org.argeo.api.acr.ldap.LdapAttr.owner; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.security.auth.Subject; +import javax.xml.namespace.QName; + +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.ldap.NamingUtils; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.directory.CmsGroup; +import org.argeo.api.cms.directory.CmsUser; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.directory.UserDirectory; +import org.argeo.api.cms.transaction.WorkTransaction; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.directory.ldap.LdapEntry; +import org.argeo.cms.directory.ldap.SharedSecret; +import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin; +import org.argeo.cms.osgi.useradmin.TokenUtils; +import org.argeo.cms.runtime.DirectoryConf; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** + * Canonical implementation of the people {@link CmsUserManager}. Wraps + * interaction with users and groups. + * + * In a *READ-ONLY* mode. We want to be able to: + *
      + *
    • Retrieve my user and corresponding information (main info, + * groups...)
    • + *
    • List all local groups (not the system roles)
    • + *
    • If sufficient rights: retrieve a given user and its information
    • + *
    + */ +public class CmsUserManagerImpl implements CmsUserManager { + private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class); + + private UserAdmin userAdmin; +// private Map serviceProperties; + private WorkTransaction userTransaction; + + private final String[] knownProps = { LdapAttr.cn.name(), LdapAttr.sn.name(), LdapAttr.givenName.name(), + LdapAttr.uid.name() }; + +// private Map> userDirectories = Collections +// .synchronizedMap(new LinkedHashMap<>()); + + private Set userDirectories = new HashSet<>(); + + public void start() { + log.debug(() -> "CMS user manager available"); + } + + public void stop() { + + } + + @Override + public String getMyMail() { + return getUserMail(CurrentUser.getUsername()); + } + + @Override + public Role[] getRoles(String filter) { + try { + return userAdmin.getRoles(filter); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Invalid filter " + filter, e); + } + } + + // ALL USER: WARNING access to this will be later reduced + + /** Retrieve a user given his dn, or null if it doesn't exist. */ + public CmsUser getUser(String dn) { + return (CmsUser) getUserAdmin().getRole(dn); + } + + /** Can be a group or a user */ + public String getUserDisplayName(String dn) { + // FIXME: during initialisation phase, the system logs "admin" as user + // name rather than the corresponding dn + if ("admin".equals(dn)) + return "System Administrator"; + else + return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn); + } + + @Override + public String getUserMail(String dn) { + return UserAdminUtils.getUserMail(getUserAdmin(), dn); + } + + /** Lists all roles of the given user */ + @Override + public String[] getUserRoles(String dn) { + Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn)); + return currAuth.getRoles(); + } + + @Override + public boolean isUserInRole(String userDn, String roleDn) { + String[] roles = getUserRoles(userDn); + for (String role : roles) { + if (role.equalsIgnoreCase(roleDn)) + return true; + } + return false; + } + + public Set listUsersInGroup(String groupDn, String filter) { + Group group = (Group) userAdmin.getRole(groupDn); + if (group == null) + throw new IllegalArgumentException("Group " + groupDn + " not found"); + Set users = new HashSet<>(); + addUsers(users, group, filter); + return users; + } + +// @Override +// public Set listAccounts(HierarchyUnit hierarchyUnit, boolean deep) { +// if(!hierarchyUnit.isFunctional()) +// throw new IllegalArgumentException("Hierarchy unit "+hierarchyUnit.getBase()+" is not functional"); +// UserDirectory directory = (UserDirectory)hierarchyUnit.getDirectory(); +// Set res = new HashSet<>(); +// for(HierarchyUnit technicalHu:hierarchyUnit.getDirectHierarchyUnits(false)) { +// if(technicalHu.isFunctional()) +// continue; +// for(Role role:directory.getHierarchyUnitRoles(technicalHu, null, false)) { +// if(role) +// } +// } +// return res; +// } + + /** Recursively add users to list */ + private void addUsers(Set users, Group group, String filter) { + Role[] roles = group.getMembers(); + for (Role role : roles) { + if (role.getType() == Role.GROUP) { + addUsers(users, (CmsGroup) role, filter); + } else if (role.getType() == Role.USER) { + if (match(role, filter)) + users.add((CmsUser) role); + } else { + // ignore + } + } + } + + public List listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) { + Role[] roles = null; + try { + roles = getUserAdmin().getRoles(filter); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e); + } + + List users = new ArrayList<>(); + for (Role role : roles) { + if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role) + && (includeSystemRoles + || !role.getName().toLowerCase().endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))) { + if (match(role, filter)) + users.add((CmsUser) role); + } + } + return users; + } + + private boolean match(Role role, String filter) { + boolean doFilter = filter != null && !"".equals(filter); + if (doFilter) { + for (String prop : knownProps) { + Object currProp = null; + try { + currProp = role.getProperties().get(prop); + } catch (Exception e) { + throw e; + } + if (currProp != null) { + String currPropStr = ((String) currProp).toLowerCase(); + if (currPropStr.contains(filter.toLowerCase())) { + return true; + } + } + } + return false; + } else + return true; + } + + @Override + public CmsUser getUserFromLocalId(String localId) { + CmsUser user = (CmsUser) getUserAdmin().getUser(LdapAttr.uid.name(), localId); + if (user == null) + user = (CmsUser) getUserAdmin().getUser(LdapAttr.cn.name(), localId); + return user; + } + + @Override + public String buildDefaultDN(String localId, int type) { + return buildDistinguishedName(localId, getDefaultDomainName(), type); + } + + /* + * EDITION + */ + @Override + public CmsUser createUser(String username, Map properties, Map credentials) { + try { + userTransaction.begin(); + CmsUser user = (CmsUser) userAdmin.createRole(username, Role.USER); + if (properties != null) { + for (String key : properties.keySet()) + user.getProperties().put(key, properties.get(key)); + } + if (credentials != null) { + for (String key : credentials.keySet()) + user.getCredentials().put(key, credentials.get(key)); + } + userTransaction.commit(); + return user; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot create user " + username, e); + } + } + + @Override + public CmsGroup createGroup(String dn) { + try { + userTransaction.begin(); + CmsGroup group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP); + userTransaction.commit(); + return group; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot create group " + dn, e); + } + } + + @Override + public CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName) { + String dn = LdapAttr.cn.name() + "=" + commonName + "," + groups.getBase(); + CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn); + if (group != null) + return group; + try { + userTransaction.begin(); + group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP); + userTransaction.commit(); + return group; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot create group " + commonName + " in " + groups, e); + } + } + + @Override + public CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole) { + String dn = LdapAttr.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole) + "," + roles.getBase(); + CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn); + if (group != null) + return group; + try { + userTransaction.begin(); + group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP); + userTransaction.commit(); + return group; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot create system role " + systemRole + " in " + roles, e); + } + } + + @Override + public HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path) { + HierarchyUnit hi = directory.getHierarchyUnit(path); + if (hi != null) + return hi; + try { + userTransaction.begin(); + HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path); + userTransaction.commit(); + return hierarchyUnit; + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1); + } + } + + @Override + public void addObjectClasses(Role role, Set objectClasses, Map additionalProperties) { + try { + userTransaction.begin(); + LdapEntry.addObjectClasses(role.getProperties(), objectClasses); + for (String key : additionalProperties.keySet()) { + role.getProperties().put(key, additionalProperties.get(key)); + } + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + role, e1); + } + } + + @Override + public void addObjectClasses(HierarchyUnit hierarchyUnit, Set objectClasses, + Map additionalProperties) { + try { + userTransaction.begin(); + LdapEntry.addObjectClasses(hierarchyUnit.getProperties(), objectClasses); + for (String key : additionalProperties.keySet()) { + hierarchyUnit.getProperties().put(key, additionalProperties.get(key)); + } + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + hierarchyUnit, e1); + } + } + + @Override + public void edit(Runnable action) { + Objects.requireNonNull(action); + try { + userTransaction.begin(); + action.run(); + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot edit", e1); + } + } + + @Override + public void addMember(CmsGroup group, Role role) { + try { + userTransaction.begin(); + group.addMember(role); + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add member " + role + " to group " + group, e1); + } + } + + @Override + public void removeMember(CmsGroup group, Role role) { + try { + userTransaction.begin(); + group.removeMember(role); + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot remove member " + role + " from group " + group, e1); + } + } + + @Override + public String getDefaultDomainName() { + Map dns = getKnownBaseDns(true); + if (dns.size() == 1) + return dns.keySet().iterator().next(); + else + throw new IllegalStateException("Current context contains " + dns.size() + " base dns: " + + dns.keySet().toString() + ". Unable to chose a default one."); + } + + public Map getKnownBaseDns(boolean onlyWritable) { + Map dns = new HashMap(); + for (UserDirectory userDirectory : userDirectories) { + Boolean readOnly = userDirectory.isReadOnly(); + String baseDn = userDirectory.getBase(); + + if (onlyWritable && readOnly) + continue; + if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN)) + continue; + if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN)) + continue; + dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectory.getProperties()).toString()); + + } + return dns; + } + + public Set getUserDirectories() { + TreeSet res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase())); + res.addAll(userDirectories); + return res; + } + + public String buildDistinguishedName(String localId, String baseDn, int type) { + Map dns = getKnownBaseDns(true); + Dictionary props = DirectoryConf.uriAsProperties(dns.get(baseDn)); + String dn = null; + if (Role.GROUP == type) + dn = LdapAttr.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn; + else if (Role.USER == type) + dn = LdapAttr.uid.name() + "=" + localId + "," + DirectoryConf.userBase.getValue(props) + "," + baseDn; + else + throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId); + return dn; + } + + @Override + public void changeOwnPassword(char[] oldPassword, char[] newPassword) { + String name = CurrentUser.getUsername(); + LdapName dn; + try { + dn = new LdapName(name); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Invalid user dn " + name, e); + } + User user = (User) userAdmin.getRole(dn.toString()); + if (!user.hasCredential(null, oldPassword)) + throw new IllegalArgumentException("Invalid password"); + if (Arrays.equals(newPassword, new char[0])) + throw new IllegalArgumentException("New password empty"); + try { + userTransaction.begin(); + user.getCredentials().put(null, newPassword); + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot change password", e); + } + } + + public void resetPassword(String username, char[] newPassword) { + LdapName dn; + try { + dn = new LdapName(username); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Invalid user dn " + username, e); + } + User user = (User) userAdmin.getRole(dn.toString()); + if (Arrays.equals(newPassword, new char[0])) + throw new IllegalArgumentException("New password empty"); + try { + userTransaction.begin(); + user.getCredentials().put(null, newPassword); + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot change password", e); + } + } + + public String addSharedSecret(String email, int hours) { + User user = (User) userAdmin.getUser(LdapAttr.mail.name(), email); + try { + userTransaction.begin(); + String uuid = UUID.randomUUID().toString(); + SharedSecret sharedSecret = new SharedSecret(hours, uuid); + user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword()); + String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue(); + userTransaction.commit(); + return tokenStr; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot change password", e); + } + } + + @Deprecated + public String addSharedSecret(String username, String authInfo, String authToken) { + try { + userTransaction.begin(); + User user = (User) userAdmin.getRole(username); + SharedSecret sharedSecret = new SharedSecret(authInfo, authToken); + user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword()); + String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue(); + userTransaction.commit(); + return tokenStr; + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add shared secret", e1); + } + } + + @Override + public void expireAuthToken(String token) { + try { + userTransaction.begin(); + String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN; + Group tokenGroup = (Group) userAdmin.getRole(dn); + String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC)); + tokenGroup.getProperties().put(description.name(), ldapDate); + userTransaction.commit(); + if (log.isDebugEnabled()) + log.debug("Token " + token + " expired."); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot expire token", e1); + } + } + + @Override + public void expireAuthTokens(Subject subject) { + Set tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN); + for (String token : tokens) + expireAuthToken(token); + } + + @Override + public void addAuthToken(String userDn, String token, Integer hours, String... roles) { + addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles); + } + + @Override + public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) { + try { + userTransaction.begin(); + User user = (User) userAdmin.getRole(userDn); + String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN; + Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP); + if (roles != null) + for (String role : roles) { + Role r = userAdmin.getRole(role); + if (r != null) + tokenGroup.addMember(r); + else { + if (!role.equals(CmsConstants.ROLE_USER)) { + throw new IllegalStateException( + "Cannot add role " + role + " to token " + token + " for " + userDn); + } + } + } + tokenGroup.getProperties().put(owner.name(), user.getName()); + if (expiryDate != null) { + String ldapDate = NamingUtils.instantToLdapDate(expiryDate); + tokenGroup.getProperties().put(description.name(), ldapDate); + } + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add token", e1); + } + } + + @Override + public UserDirectory getDirectory(Role user) { + String name = user.getName(); + NavigableMap possible = new TreeMap<>(); + for (UserDirectory userDirectory : userDirectories) { + if (name.endsWith(userDirectory.getBase())) { + possible.put(userDirectory.getBase(), userDirectory); + } + } + if (possible.size() == 0) + throw new IllegalStateException("No user directory found for user " + name); + return possible.lastEntry().getValue(); + } + +// public User createUserFromPerson(Node person) { +// String email = JcrUtils.get(person, LdapAttrs.mail.property()); +// String dn = buildDefaultDN(email, Role.USER); +// User user; +// try { +// userTransaction.begin(); +// user = (User) userAdmin.createRole(dn, Role.USER); +// Dictionary userProperties = user.getProperties(); +// String name = JcrUtils.get(person, LdapAttrs.displayName.property()); +// userProperties.put(LdapAttrs.cn.name(), name); +// userProperties.put(LdapAttrs.displayName.name(), name); +// String givenName = JcrUtils.get(person, LdapAttrs.givenName.property()); +// String surname = JcrUtils.get(person, LdapAttrs.sn.property()); +// userProperties.put(LdapAttrs.givenName.name(), givenName); +// userProperties.put(LdapAttrs.sn.name(), surname); +// userProperties.put(LdapAttrs.mail.name(), email.toLowerCase()); +// userTransaction.commit(); +// } catch (Exception e) { +// try { +// userTransaction.rollback(); +// } catch (Exception e1) { +// log.error("Could not roll back", e1); +// } +// if (e instanceof RuntimeException) +// throw (RuntimeException) e; +// else +// throw new RuntimeException("Cannot create user", e); +// } +// return user; +// } + + public UserAdmin getUserAdmin() { + return userAdmin; + } + +// public UserTransaction getUserTransaction() { +// return userTransaction; +// } + + /* DEPENDENCY INJECTION */ + public void setUserAdmin(UserAdmin userAdmin) { + this.userAdmin = userAdmin; + + if (userAdmin instanceof AggregatingUserAdmin) { + userDirectories = ((AggregatingUserAdmin) userAdmin).getUserDirectories(); + } else { + throw new IllegalArgumentException("Only " + AggregatingUserAdmin.class.getName() + " is supported."); + } + +// this.serviceProperties = serviceProperties; + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + +// public void addUserDirectory(UserDirectory userDirectory, Map properties) { +// userDirectories.put(userDirectory, new Hashtable<>(properties)); +// } +// +// public void removeUserDirectory(UserDirectory userDirectory, Map properties) { +// userDirectories.remove(userDirectory); +// } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java new file mode 100644 index 000000000..0fd0a63ed --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java @@ -0,0 +1,65 @@ +package org.argeo.cms.internal.runtime; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.cms.acr.CmsContentRepository; +import org.argeo.cms.acr.directory.DirectoryContentProvider; +import org.argeo.cms.acr.fs.FsContentProvider; + +public class DeployedContentRepository extends CmsContentRepository { + private final static String ROOT_XML = "cr:root.xml"; + + private final static CmsLog log = CmsLog.getLog(DeployedContentRepository.class); + + private CmsUserManager userManager; + + @Override + public void start() { + long begin = System.currentTimeMillis(); + try { + super.start(); + Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML); + initRootContentProvider(null); + +// Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE); +// FsContentProvider srvContentProvider = new FsContentProvider("/" + CmsConstants.SRV_WORKSPACE, srvPath, false); +// addProvider(srvContentProvider); + + // run dir + Path runDirPath = KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE); + if (runDirPath != null) { + Files.createDirectories(runDirPath); + FsContentProvider runContentProvider = new FsContentProvider(CmsContentRepository.RUN_BASE, runDirPath); + addProvider(runContentProvider); + } + + // users + DirectoryContentProvider directoryContentProvider = new DirectoryContentProvider( + CmsContentRepository.DIRECTORY_BASE, userManager); + addProvider(directoryContentProvider); + + // remote +// DavContentProvider davContentProvider = new DavContentProvider("/srv", +// URI.create("http://localhost/unstable/a2/")); +// addProvider(davContentProvider); + } catch (IOException e) { + throw new IllegalStateException("Cannot start content repository", e); + } + long duration = System.currentTimeMillis() - begin; + log.debug(() -> "CMS content repository available (initialisation took " + duration + " ms)"); + } + + @Override + public void stop() { + super.stop(); + } + + public void setUserManager(CmsUserManager userManager) { + this.userManager = userManager; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java deleted file mode 100644 index 70ea9ec48..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.argeo.cms.internal.runtime; - -import static org.argeo.cms.internal.runtime.KernelUtils.getFrameworkProp; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.io.Reader; -import java.net.InetAddress; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.List; - -import javax.security.auth.x500.X500Principal; - -import org.apache.commons.io.FileUtils; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.http.InternalHttpConstants; -import org.argeo.osgi.useradmin.UserAdminConf; - -/** - * Interprets framework properties in order to generate the initial deploy - * configuration. - */ -public class InitUtils { - private final static CmsLog log = CmsLog.getLog(InitUtils.class); - - /** Override the provided config with the framework properties */ - public static Dictionary getHttpServerConfig(Dictionary 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 props = new Hashtable(); - // 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> getUserDirectoryConfigs() { - List> res = new ArrayList<>(); - File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile(); - List 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 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 index dfe86cfaa..e6ca1ba60 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java @@ -3,51 +3,36 @@ package org.argeo.cms.internal.runtime; import org.argeo.api.cms.CmsConstants; /** Internal CMS constants. */ -public interface KernelConstants { +interface KernelConstants { // Directories - String DIR_NODE = "node"; - String DIR_REPOS = "repos"; - String DIR_INDEXES = "indexes"; - String DIR_TRANSACTIONS = "transactions"; + String DIR_PRIVATE = "private"; // Files - String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif"; - String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12"; - String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key"; - String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt"; - String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab"; + String NODE_KEY_TAB_PATH = DIR_PRIVATE + "/krb5.keytab"; + String NODE_SSHD_AUTHORIZED_KEYS_PATH = DIR_PRIVATE + "/authorized_keys"; // Security String JAAS_CONFIG = "/org/argeo/cms/internal/runtime/jaas.cfg"; String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/runtime/jaas-ipa.cfg"; - // Java - String JAAS_CONFIG_PROP = "java.security.auth.login.config"; - - // DEFAULTS JCR PATH - String DEFAULT_HOME_BASE_PATH = "/home"; - String DEFAULT_USERS_BASE_PATH = "/users"; - String DEFAULT_GROUPS_BASE_PATH = "/groups"; - // KERBEROS String DEFAULT_KERBEROS_SERVICE = "HTTP"; + String DEFAULT_KEYSTORE_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".p12"; + + String DEFAULT_TRUSTSTORE_PATH = DIR_PRIVATE + "/trusted.p12"; + + String DEFAULT_PEM_KEY_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".key"; + + String DEFAULT_PEM_CERT_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".crt"; + + String IPA_PEM_CA_CERT_PATH = "/etc/ipa/ca.crt"; + + String DEFAULT_KEYSTORE_PASSWORD = "changeit"; + + String PKCS12 = "PKCS12"; + // HTTP client - String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility"; - - // RWT / RAP - // String PATH_WORKBENCH = "/ui"; - // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public"; - -// String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config"; - String JETTY_FACTORY_PID = "org.argeo.equinox.jetty.config"; - String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern"; - // default Jetty server configured via JettyConfigurator - String DEFAULT_JETTY_SERVER = "default"; - String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; - - // avoid dependencies - String CONTEXT_NAME_PROP = "contextName"; - String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; - String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; + // String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility"; + } 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 index afcb9ff26..6e47873b3 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java @@ -1,6 +1,5 @@ package org.argeo.cms.internal.runtime; -import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.net.URI; @@ -19,7 +18,7 @@ import org.argeo.api.cms.CmsLog; import org.argeo.cms.internal.osgi.CmsActivator; /** Package utilities */ -public class KernelUtils implements KernelConstants { +class KernelUtils implements KernelConstants { final static String OSGI_INSTANCE_AREA = "osgi.instance.area"; final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; @@ -52,41 +51,28 @@ public class KernelUtils implements KernelConstants { return asDictionary(props); } - static File getExecutionDir(String relativePath) { - File executionDir = new File(getFrameworkProp("user.dir")); + static Path getExecutionDir(String relativePath) { + Path executionDir = Paths.get(getFrameworkProp("user.dir")); if (relativePath == null) return executionDir; - try { - return new File(executionDir, relativePath).getCanonicalFile(); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot get canonical file", e); - } - } - - static File getOsgiInstanceDir() { - return new File(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) - .getAbsoluteFile(); + return executionDir.resolve(relativePath); } public static Path getOsgiInstancePath(String relativePath) { - return Paths.get(getOsgiInstanceUri(relativePath)); + URI uri = getOsgiInstanceUri(relativePath); + if (uri == null) // no data area available + return null; + return Paths.get(uri); } public static URI getOsgiInstanceUri(String relativePath) { String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA); - if (osgiInstanceBaseUri != null) - return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); - else - return Paths.get(System.getProperty("user.dir")).toUri(); - } + if (osgiInstanceBaseUri == null) // no data area available + return null; - static File getOsgiConfigurationFile(String relativePath) { - try { - return new File(new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) - .getCanonicalFile(); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e); - } + if (!osgiInstanceBaseUri.endsWith("/")) + osgiInstanceBaseUri = osgiInstanceBaseUri + "/"; + return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); } static String getFrameworkProp(String key, String def) { @@ -100,36 +86,14 @@ public class KernelUtils implements KernelConstants { return value; } - public static String getFrameworkProp(String key) { + static String getFrameworkProp(String key) { return getFrameworkProp(key, null); } - // Security - // static Subject anonymousLogin() { - // Subject subject = new Subject(); - // LoginContext lc; - // try { - // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject); - // lc.login(); - // return subject; - // } catch (LoginException e) { - // throw new CmsException("Cannot login as anonymous", e); - // } - // } - static void logFrameworkProperties(CmsLog log) { for (Object sysProp : new TreeSet(System.getProperties().keySet())) { log.debug(sysProp + "=" + getFrameworkProp(sysProp.toString())); } - // String[] keys = { Constants.FRAMEWORK_STORAGE, - // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION, - // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY, - // Constants.FRAMEWORK_TRUST_REPOSITORIES, - // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR, - // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN, - // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID }; - // for (String key : keys) - // log.debug(key + "=" + bc.getProperty(key)); } static void printSystemProperties(PrintStream out) { @@ -140,84 +104,6 @@ public class KernelUtils implements KernelConstants { out.println(key + "=" + display.get(key)); } -// static Session openAdminSession(Repository repository) { -// return openAdminSession(repository, null); -// } -// -// static Session openAdminSession(final Repository repository, final String workspaceName) { -// LoginContext loginContext = loginAsDataAdmin(); -// return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { -// -// @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() { -// -// @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; 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 index 474a89950..378146ea1 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java @@ -1,84 +1,38 @@ package org.argeo.cms.internal.runtime; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; -import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; +import java.security.KeyFactory; import java.security.KeyStore; +import java.security.KeyStore.TrustedCertificateEntry; import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.SecureRandom; -import java.security.Security; -import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.Date; - -import javax.security.auth.x500.X500Principal; - -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.InputDecryptorProvider; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; -import org.bouncycastle.pkcs.PKCSException; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Collection; +import java.util.Objects; /** * Utilities around private keys and certificate, mostly wrapping BouncyCastle * implementations. */ class PkiUtils { - final static String PKCS12 = "PKCS12"; - - private final static String SECURITY_PROVIDER; - static { - Security.addProvider(new BouncyCastleProvider()); - SECURITY_PROVIDER = "BC"; - } - - public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal, - int keySize, char[] keyPassword) { - try { - KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER); - kpGen.initialize(keySize, new SecureRandom()); - KeyPair pair = kpGen.generateKeyPair(); - Date notBefore = new Date(System.currentTimeMillis() - 10000); - Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000); - BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); - X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore, - notAfter, x500Principal, pair.getPublic()); - ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER) - .build(pair.getPrivate()); - X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) - .getCertificate(certGen.build(sigGen)); - cert.checkValidity(new Date()); - cert.verify(cert.getPublicKey()); - - keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert }); - return cert; - } catch (GeneralSecurityException | OperatorCreationException e) { - throw new RuntimeException("Cannot generate self-signed certificate", e); - } - } - public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) { try { - KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER); + KeyStore store = KeyStore.getInstance(keyStoreType); if (Files.exists(keyStoreFile)) { try (InputStream fis = Files.newInputStream(keyStoreFile)) { store.load(fis, keyStorePassword); @@ -102,158 +56,67 @@ class PkiUtils { } } -// public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password) -// throws Exception { -// // Get the private key -// FileReader reader = new FileReader(keyFile); -// -// PEMReader pem = new PemReader(reader, new PasswordFinder() { -// @Override -// public char[] getPassword() { -// return password.toCharArray(); -// } -// }); -// -// PrivateKey key = ((KeyPair) pem.readObject()).getPrivate(); -// -// pem.close(); -// reader.close(); -// -// // Get the certificate -// reader = new FileReader(cerFile); -// pem = new PEMReader(reader); -// -// X509Certificate cert = (X509Certificate) pem.readObject(); -// -// pem.close(); -// reader.close(); -// -// // Put them into a PKCS12 keystore and write it to a byte[] -// ByteArrayOutputStream bos = new ByteArrayOutputStream(); -// KeyStore ks = KeyStore.getInstance("PKCS12"); -// ks.load(null); -// ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert }); -// ks.store(bos, password.toCharArray()); -// bos.close(); -// return bos.toByteArray(); -// } + public static void loadPrivateCertificatePem(KeyStore keyStore, String alias, Reader key, char[] keyPassword, + BufferedInputStream cert) { + Objects.requireNonNull(keyStore); + Objects.requireNonNull(key); + try { + X509Certificate certificate = loadPemCertificate(cert); + PrivateKey privateKey = loadPemPrivateKey(key, keyPassword); + keyStore.setKeyEntry(alias, privateKey, keyPassword, new java.security.cert.Certificate[] { certificate }); + } catch (KeyStoreException e) { + throw new RuntimeException("Cannot store PEM certificate", e); + } + } - public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) { - PrivateKey privateKey = loadPemPrivateKey(key, keyPassword); - X509Certificate certificate = loadPemCertificate(cert); + public static void loadTrustedCertificatePem(KeyStore keyStore, char[] keyStorePassword, BufferedInputStream cert) { try { - keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword, - new java.security.cert.Certificate[] { certificate }); + X509Certificate certificate = loadPemCertificate(cert); + TrustedCertificateEntry trustedCertificateEntry = new TrustedCertificateEntry(certificate); + keyStore.setEntry(certificate.getSubjectX500Principal().getName(), trustedCertificateEntry, null); } catch (KeyStoreException e) { throw new RuntimeException("Cannot store PEM certificate", e); } } public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) { - try (PEMParser pemParser = new PEMParser(reader)) { - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); - Object object = pemParser.readObject(); - PrivateKeyInfo privateKeyInfo; - if (object instanceof PKCS8EncryptedPrivateKeyInfo) { - if (keyPassword == null) - throw new IllegalArgumentException("A key password is required"); - InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword); - privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv); - } else if (object instanceof PrivateKeyInfo) { - privateKeyInfo = (PrivateKeyInfo) object; - } else { - throw new IllegalArgumentException("Unsupported format for private key"); + try { + StringBuilder key = new StringBuilder(); + try (BufferedReader in = new BufferedReader(reader)) { + String line = in.readLine(); + if (!"-----BEGIN PRIVATE KEY-----".equals(line)) + throw new IllegalArgumentException("Not a PEM private key"); + lines: while ((line = in.readLine()) != null) { + if ("-----END PRIVATE KEY-----".equals(line)) + break lines; + key.append(line); + } } - return converter.getPrivateKey(privateKeyInfo); - } catch (IOException | OperatorCreationException | PKCSException e) { - throw new RuntimeException("Cannot read private key", e); - } - } - public static X509Certificate loadPemCertificate(Reader reader) { - try (PEMParser pemParser = new PEMParser(reader)) { - X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject(); - X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) - .getCertificate(certHolder); - return cert; - } catch (IOException | CertificateException e) { - throw new RuntimeException("Cannot read private key", e); - } - } + byte[] encoded = Base64.getDecoder().decode(key.toString()); - public static void main(String[] args) throws Exception { - final String ALGORITHM = "RSA"; - final String provider = "BC"; - SecureRandom secureRandom = new SecureRandom(); - long begin = System.currentTimeMillis(); - for (int i = 512; i < 1024; i = i + 2) { - try { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider); - keyGen.initialize(i, secureRandom); - keyGen.generateKeyPair(); - } catch (Exception e) { - System.err.println(i + " : " + e.getMessage()); - } + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) { + throw new RuntimeException("Cannot load PEM key", e); } - System.out.println((System.currentTimeMillis() - begin) + " ms"); - - // // String text = "a"; - // String text = - // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest"; - // try { - // System.out.println(text); - // PrivateKey privateKey; - // PublicKey publicKey; - // char[] password = "changeit".toCharArray(); - // String alias = "CN=test"; - // KeyStore keyStore = KeyStore.getInstance("pkcs12"); - // File p12file = new File("test.p12"); - // p12file.delete(); - // if (!p12file.exists()) { - // keyStore.load(null); - // generateSelfSignedCertificate(keyStore, new X500Principal(alias), - // 513, password); - // try (OutputStream out = new FileOutputStream(p12file)) { - // keyStore.store(out, password); - // } - // } - // try (InputStream in = new FileInputStream(p12file)) { - // keyStore.load(in, password); - // privateKey = (PrivateKey) keyStore.getKey(alias, password); - // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey(); - // } - // // KeyPair key; - // // final KeyPairGenerator keyGen = - // // KeyPairGenerator.getInstance(ALGORITHM); - // // keyGen.initialize(4096, new SecureRandom()); - // // long begin = System.currentTimeMillis(); - // // key = keyGen.generateKeyPair(); - // // System.out.println((System.currentTimeMillis() - begin) + " ms"); - // // keyStore.load(null); - // // keyStore.setKeyEntry("test", key.getPrivate(), password, null); - // // try(OutputStream out=new FileOutputStream(p12file)) { - // // keyStore.store(out, password); - // // } - // // privateKey = key.getPrivate(); - // // publicKey = key.getPublic(); - // - // Cipher encrypt = Cipher.getInstance(ALGORITHM); - // encrypt.init(Cipher.ENCRYPT_MODE, publicKey); - // byte[] encrypted = encrypt.doFinal(text.getBytes()); - // String encryptedBase64 = - // Base64.getEncoder().encodeToString(encrypted); - // System.out.println(encryptedBase64); - // byte[] encryptedFromBase64 = - // Base64.getDecoder().decode(encryptedBase64); - // - // Cipher decrypt = Cipher.getInstance(ALGORITHM); - // decrypt.init(Cipher.DECRYPT_MODE, privateKey); - // byte[] decrypted = decrypt.doFinal(encryptedFromBase64); - // System.out.println(new String(decrypted)); - // } catch (Exception e) { - // e.printStackTrace(); - // } } + public static X509Certificate loadPemCertificate(BufferedInputStream in) { + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); + @SuppressWarnings("unchecked") + Collection certificates = (Collection) certificateFactory + .generateCertificates(in); + if (certificates.isEmpty()) + throw new IllegalArgumentException("No certificate found"); + if (certificates.size() != 1) + throw new IllegalArgumentException(certificates.size() + " certificates found"); + return certificates.iterator().next(); + } catch (CertificateException e) { + throw new IllegalStateException("cannot load certifciate", e); + } + } } 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 index c7c804c64..51db582c6 100644 --- 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 @@ -1,8 +1,10 @@ USER { org.argeo.cms.auth.RemoteSessionLoginModule sufficient; org.argeo.cms.auth.SpnegoLoginModule optional; - com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true; - org.argeo.cms.auth.UserAdminLoginModule sufficient; + com.sun.security.auth.module.Krb5LoginModule optional + tryFirstPass=true + storeKey=true; + org.argeo.cms.auth.UserAdminLoginModule required; }; ANONYMOUS { @@ -16,7 +18,7 @@ DATA_ADMIN { NODE { com.sun.security.auth.module.Krb5LoginModule optional - keyTab="${osgi.instance.area}node/krb5.keytab" + keyTab="${osgi.instance.area}private/krb5.keytab" useKeyTab=true storeKey=true; org.argeo.cms.auth.DataAdminLoginModule requisite; @@ -28,10 +30,8 @@ KEYRING { SINGLE_USER { com.sun.security.auth.module.Krb5LoginModule optional - principal="${user.name}" storeKey=true - useTicketCache=true - debug=true; + useTicketCache=true; org.argeo.cms.auth.SingleUserLoginModule requisite; }; 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 index 364977d4b..8290fb339 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg @@ -1,7 +1,7 @@ USER { org.argeo.cms.auth.RemoteSessionLoginModule sufficient; org.argeo.cms.auth.IdentLoginModule optional; - org.argeo.cms.auth.UserAdminLoginModule requisite; + org.argeo.cms.auth.UserAdminLoginModule required; }; ANONYMOUS { diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java b/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java index 6c195d45f..f60d3352e 100644 --- a/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java +++ b/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; @@ -17,8 +16,8 @@ import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; -import org.apache.commons.io.IOUtils; -import org.argeo.api.cms.CmsTheme; +import org.argeo.api.cms.ux.CmsTheme; +import org.argeo.cms.util.StreamUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -32,11 +31,17 @@ import org.osgi.framework.BundleContext; * / ** /*.{png,gif,jpeg,...}.
    */ public class BundleCmsTheme implements CmsTheme { - public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2"; +// public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2"; - public final static String CMS_THEME_PROPERTY = "argeo.cms.theme"; +// public final static String CMS_THEME_PROPERTY = "argeo.cms.theme"; + @Deprecated public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle"; + /** Declared theme ID, to be used by OSGi services to reference it as parent. */ + public final static String THEME_ID_PROPERTY = "themeId"; + public final static String SMALL_ICON_SIZE_PROPERTY = "smallIconSize"; + public final static String BIG_ICON_SIZE_PROPERTY = "bigIconSize"; + private final static String HEADER_CSS = "header.css"; private final static String FONTS_TXT = "fonts.txt"; private final static String BODY_HTML = "body.html"; @@ -46,6 +51,8 @@ public class BundleCmsTheme implements CmsTheme { private CmsTheme parentTheme; private String themeId; + private String declaredThemeId;; + private Set webCssPaths = new TreeSet<>(); private Set rapCssPaths = new TreeSet<>(); private Set swtCssPaths = new TreeSet<>(); @@ -64,13 +71,20 @@ public class BundleCmsTheme implements CmsTheme { // private String swtCssPath; private Bundle themeBundle; - private Integer defaultIconSize = 16; + private Integer smallIconSize = 16; + private Integer bigIconSize = 32; public BundleCmsTheme() { } public void init(BundleContext bundleContext, Map properties) { + declaredThemeId = properties.get(THEME_ID_PROPERTY); + if (properties.containsKey(SMALL_ICON_SIZE_PROPERTY)) + smallIconSize = Integer.valueOf(properties.get(SMALL_ICON_SIZE_PROPERTY)); + if (properties.containsKey(BIG_ICON_SIZE_PROPERTY)) + smallIconSize = Integer.valueOf(properties.get(BIG_ICON_SIZE_PROPERTY)); + initResources(bundleContext, null); } @@ -103,6 +117,10 @@ public class BundleCmsTheme implements CmsTheme { // swtCssPath = "/swt/"; // this.themeId = RWT.DEFAULT_THEME_ID; this.themeId = themeBundle.getSymbolicName(); + if (declaredThemeId != null && !declaredThemeId.equals(themeId)) + throw new IllegalArgumentException( + "Declared theme id " + declaredThemeId + " is different from " + themeId); + webCssPaths = addCss(themeBundle, "/css/"); rapCssPaths = addCss(themeBundle, "/rap/"); swtCssPaths = addCss(themeBundle, "/swt/"); @@ -212,7 +230,7 @@ public class BundleCmsTheme implements CmsTheme { void loadBodyHtml(URL url) { try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { - bodyHtml = IOUtils.toString(url, StandardCharsets.UTF_8); + bodyHtml = StreamUtils.toString(in); } catch (IOException e) { throw new IllegalArgumentException("Cannot load URL " + url, e); } @@ -320,17 +338,27 @@ public class BundleCmsTheme implements CmsTheme { } @Override - public Integer getDefaultIconSize() { - return defaultIconSize; + public int getSmallIconSize() { + return smallIconSize; + } + + @Override + public int getBigIconSize() { + return bigIconSize; } @Override public InputStream loadPath(String path) throws IOException { URL url = themeBundle.getResource(path); - if (url == null) - throw new IllegalArgumentException( - "Path " + path + " not found in bundle " + themeBundle.getSymbolicName()); - return url.openStream(); + if (url == null) { + if (parentTheme != null) + return parentTheme.loadPath(path); + else + throw new IllegalArgumentException( + "Path " + path + " not found in bundle " + themeBundle.getSymbolicName()); + } else { + return url.openStream(); + } } private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) { @@ -363,4 +391,12 @@ public class BundleCmsTheme implements CmsTheme { this.parentTheme = parentTheme; } + public void setSmallIconSize(Integer smallIconSize) { + this.smallIconSize = smallIconSize; + } + + public void setBigIconSize(Integer bigIconSize) { + this.bigIconSize = bigIconSize; + } + } diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java deleted file mode 100644 index 424d62f68..000000000 --- a/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.cms.osgi; - -import java.util.Collection; - -import javax.security.auth.Subject; - -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.CmsSessionId; -import org.osgi.framework.BundleContext; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -public class CmsOsgiUtils { - - /** @return The {@link CmsSession} for this {@link Subject} or null. */ - public static CmsSession getCmsSession(BundleContext bc, Subject subject) { - if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty()) - return null; - CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next(); - String uuid = cmsSessionId.getUuid().toString(); - Collection> 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 cmsSessionRef; - if (sr.size() == 1) { - cmsSessionRef = sr.iterator().next(); - return bc.getService(cmsSessionRef); - } else if (sr.size() == 0) { - return null; - } else - throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid); - } - - /** Singleton.*/ - private CmsOsgiUtils() { - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/FilterRequirement.java b/org.argeo.cms/src/org/argeo/cms/osgi/FilterRequirement.java new file mode 100644 index 000000000..5582c3481 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/FilterRequirement.java @@ -0,0 +1,42 @@ +package org.argeo.cms.osgi; + +import java.util.HashMap; +import java.util.Map; + +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +/** Simplify filtering resources. */ +public class FilterRequirement implements Requirement { + private String namespace; + private String filter; + + public FilterRequirement(String namespace, String filter) { + this.namespace = namespace; + this.filter = filter; + } + + @Override + public Resource getResource() { + return null; + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public Map getDirectives() { + Map directives = new HashMap<>(); + directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter); + return directives; + } + + @Override + public Map getAttributes() { + return new HashMap<>(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java new file mode 100644 index 000000000..72c4336e3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java @@ -0,0 +1,78 @@ +package org.argeo.cms.osgi.useradmin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.security.auth.x500.X500Principal; + +import org.argeo.api.cms.directory.CmsAuthorization; +import org.osgi.service.useradmin.Authorization; + +/** An {@link Authorization} which combines roles form various auth sources. */ +class AggregatingAuthorization implements CmsAuthorization { + private final String name; + private final String displayName; + private final Set systemRoles; + private final Set roles; + + public AggregatingAuthorization(String name, String displayName, Set systemRoles, String[] roles) { + this.name = new X500Principal(name).getName(); + this.displayName = displayName; + this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles)); + Set 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 res = new ArrayList(size); + res.addAll(systemRoles); + res.addAll(roles); + return res.toArray(new String[size]); + } + + @Override + public int hashCode() { + if (name == null) + return super.hashCode(); + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Authorization)) + return false; + Authorization that = (Authorization) obj; + if (name == null) + return that.getName() == null; + return name.equals(that.getName()); + } + + @Override + public String toString() { + return displayName; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingUserAdmin.java new file mode 100644 index 000000000..8ebb98e3a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingUserAdmin.java @@ -0,0 +1,330 @@ +package org.argeo.cms.osgi.useradmin; + +import static org.argeo.cms.osgi.useradmin.DirectoryUserAdmin.toLdapName; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.directory.CmsUser; +import org.argeo.api.cms.directory.UserDirectory; +import org.argeo.cms.runtime.DirectoryConf; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** + * Aggregates multiple {@link UserDirectory} and integrates them with system + * roles. + */ +public class AggregatingUserAdmin implements UserAdmin { + private final LdapName systemRolesBaseDn; + private final LdapName tokensBaseDn; + + // DAOs + private DirectoryUserAdmin systemRoles = null; + private DirectoryUserAdmin tokens = null; + private Map businessRoles = new HashMap(); + + // TODO rather use an empty constructor and an init method + public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) { + try { + this.systemRolesBaseDn = new LdapName(systemRolesBaseDn); + if (tokensBaseDn != null) + this.tokensBaseDn = new LdapName(tokensBaseDn); + else + this.tokensBaseDn = null; + } catch (InvalidNameException e) { + throw new IllegalStateException("Cannot initialize " + AggregatingUserAdmin.class, e); + } + } + + @Override + public Role createRole(String name, int type) { + return findUserAdmin(name).createRole(name, type); + } + + @Override + public boolean removeRole(String name) { + boolean actuallyDeleted = findUserAdmin(name).removeRole(name); + systemRoles.removeRole(name); + return actuallyDeleted; + } + + @Override + public Role getRole(String name) { + return findUserAdmin(name).getRole(name); + } + + @Override + public Role[] getRoles(String filter) throws InvalidSyntaxException { + List res = new ArrayList(); + 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 res = new ArrayList(); + for (UserAdmin userAdmin : businessRoles.values()) { + User u = userAdmin.getUser(key, value); + if (u != null) + res.add(u); + } + // Note: node roles cannot contain users, so it is not searched + return res.size() == 1 ? res.get(0) : null; + } + + /** Builds an authorisation by scanning all referentials. */ + @Override + public Authorization getAuthorization(User user) { + if (user == null) {// anonymous + return systemRoles.getAuthorization(null); + } + DirectoryUserAdmin userReferentialOfThisUser = findUserAdmin(user.getName()); + Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user); + User retrievedUser = (User) userReferentialOfThisUser.getRole(user.getName()); + String usernameToUse; + String displayNameToUse; + if (user instanceof Group) { + // TODO check whether this is still working + String ownerDn = TokenUtils.userDn((Group) user); + if (ownerDn != null) {// tokens + UserAdmin ownerUserAdmin = findUserAdmin(ownerDn); + User ownerUser = (User) ownerUserAdmin.getRole(ownerDn); + usernameToUse = ownerDn; + displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser); + } else { + usernameToUse = rawAuthorization.getName(); + displayNameToUse = rawAuthorization.toString(); + } + } else {// regular users + usernameToUse = rawAuthorization.getName(); + displayNameToUse = rawAuthorization.toString(); + } + + // gather roles from other referentials + List rawRoles = Arrays.asList(rawAuthorization.getRoles()); + List allRoles = new ArrayList<>(rawRoles); + for (LdapName otherBaseDn : businessRoles.keySet()) { + if (otherBaseDn.equals(userReferentialOfThisUser.getBaseDn())) + continue; + DirectoryUserAdmin otherUserAdmin = userAdminToUse(user, businessRoles.get(otherBaseDn)); + if (otherUserAdmin == null) + continue; + for (String roleStr : rawRoles) { + User role = (User) findUserAdmin(roleStr).getRole(roleStr); + Authorization auth = otherUserAdmin.getAuthorization(role); + allRoles.addAll(Arrays.asList(auth.getRoles())); + } + + } + + // integrate system roles + final DirectoryUserAdmin userAdminToUse = userAdminToUse(retrievedUser, userReferentialOfThisUser); + Objects.requireNonNull(userAdminToUse); + + try { + Set sysRoles = new HashSet(); + for (String role : rawAuthorization.getRoles()) { + User userOrGroup = (User) userAdminToUse.getRole(role); + Authorization auth = systemRoles.getAuthorization(userOrGroup); + systemRoles: for (String systemRole : auth.getRoles()) { + if (role.equals(systemRole)) + continue systemRoles; + sysRoles.add(systemRole); + } +// sysRoles.addAll(Arrays.asList(auth.getRoles())); + } + addAbstractSystemRoles(rawAuthorization, sysRoles); + Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles, + allRoles.toArray(new String[allRoles.size()])); + return authorization; + } finally { + if (userAdminToUse != null && userAdminToUse.isScoped()) { + userAdminToUse.destroy(); + } + } + } + + /** Decide whether to scope or not */ + private DirectoryUserAdmin userAdminToUse(User user, DirectoryUserAdmin userAdmin) { + if (userAdmin.isAuthenticated()) + return userAdmin; + if (user instanceof CmsUser) { + return userAdmin; + } else if (user instanceof AuthenticatingUser) { + return userAdmin.scope(user).orElse(null); + } else { + throw new IllegalArgumentException("Unsupported user type " + user.getClass()); + } + + } + + /** + * Enrich with application-specific roles which are strictly programmatic, such + * as anonymous/user semantics. + */ + protected void addAbstractSystemRoles(Authorization rawAuthorization, Set sysRoles) { + + } + + // + // USER ADMIN AGGREGATOR + // + protected void addUserDirectory(UserDirectory ud) { + if (!(ud instanceof DirectoryUserAdmin)) + throw new IllegalArgumentException("Only " + DirectoryUserAdmin.class.getName() + " is supported"); + DirectoryUserAdmin userDirectory = (DirectoryUserAdmin) ud; + String basePath = userDirectory.getBase(); + if (isSystemRolesBaseDn(basePath)) { + this.systemRoles = userDirectory; + systemRoles.setExternalRoles(this); + } else if (isTokensBaseDn(basePath)) { + this.tokens = userDirectory; + tokens.setExternalRoles(this); + } else { + LdapName baseDn = toLdapName(basePath); + if (businessRoles.containsKey(baseDn)) + throw new IllegalStateException("There is already a user admin for " + baseDn); + businessRoles.put(baseDn, userDirectory); + } + userDirectory.init(); + postAdd(userDirectory); + } + + /** Called after a new user directory has been added */ + protected void postAdd(UserDirectory userDirectory) { + } + + private DirectoryUserAdmin findUserAdmin(String name) { + try { + return findUserAdmin(new LdapName(name)); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Badly formatted name " + name, e); + } + } + + private DirectoryUserAdmin findUserAdmin(LdapName name) { + if (name.startsWith(systemRolesBaseDn)) + return systemRoles; + if (tokensBaseDn != null && name.startsWith(tokensBaseDn)) + return tokens; + List res = new ArrayList<>(1); + userDirectories: for (LdapName baseDn : businessRoles.keySet()) { + DirectoryUserAdmin userDirectory = businessRoles.get(baseDn); + if (name.startsWith(baseDn)) { + if (userDirectory.isDisabled()) + continue userDirectories; +// if (res.isEmpty()) { + res.add(userDirectory); +// } else { +// for (AbstractUserDirectory ud : res) { +// LdapName bd = ud.getBaseDn(); +// if (userDirectory.getBaseDn().startsWith(bd)) { +// // child user directory +// } +// } +// } + } + } + if (res.size() == 0) + throw new IllegalStateException("Cannot find user admin for " + name); + if (res.size() > 1) + throw new IllegalStateException("Multiple user admin found for " + name); + return res.get(0); + } + + protected boolean isSystemRolesBaseDn(String basePath) { + return toLdapName(basePath).equals(systemRolesBaseDn); + } + + protected boolean isTokensBaseDn(String basePath) { + return tokensBaseDn != null && toLdapName(basePath).equals(tokensBaseDn); + } + +// protected Dictionary currentState() { +// Dictionary res = new Hashtable(); +// // res.put(NodeConstants.CN, NodeConstants.DEFAULT); +// for (LdapName name : businessRoles.keySet()) { +// AbstractUserDirectory userDirectory = businessRoles.get(name); +// String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString(); +// res.put(uri, ""); +// } +// return res; +// } + + public void start() { + if (systemRoles == null) { + // TODO do we really need separate system roles? + Hashtable properties = new Hashtable<>(); + properties.put(DirectoryConf.baseDn.name(), "ou=roles,ou=system"); + systemRoles = new DirectoryUserAdmin(properties); + } + } + + public void stop() { + for (LdapName name : businessRoles.keySet()) { + DirectoryUserAdmin userDirectory = businessRoles.get(name); + destroy(userDirectory); + } + businessRoles.clear(); + businessRoles = null; + destroy(systemRoles); + systemRoles = null; + } + + private void destroy(DirectoryUserAdmin userDirectory) { + preDestroy(userDirectory); + userDirectory.destroy(); + } + +// protected void removeUserDirectory(UserDirectory userDirectory) { +// LdapName baseDn = toLdapName(userDirectory.getContext()); +// businessRoles.remove(baseDn); +// if (userDirectory instanceof DirectoryUserAdmin) +// destroy((DirectoryUserAdmin) userDirectory); +// } + + @Deprecated + protected void removeUserDirectory(String basePath) { + if (isSystemRolesBaseDn(basePath)) + throw new IllegalArgumentException("System roles cannot be removed "); + LdapName baseDn = toLdapName(basePath); + if (!businessRoles.containsKey(baseDn)) + throw new IllegalStateException("No user directory registered for " + baseDn); + DirectoryUserAdmin userDirectory = businessRoles.remove(baseDn); + destroy(userDirectory); + } + + /** + * Called before each user directory is destroyed, so that additional actions + * can be performed. + */ + protected void preDestroy(UserDirectory userDirectory) { + } + + public Set getUserDirectories() { + TreeSet res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase())); + res.addAll(businessRoles.values()); + res.add(systemRoles); + return res; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AuthenticatingUser.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AuthenticatingUser.java new file mode 100644 index 000000000..b87dc9bf4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AuthenticatingUser.java @@ -0,0 +1,83 @@ +package org.argeo.cms.osgi.useradmin; + +import java.util.Dictionary; +import java.util.Hashtable; + +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.directory.DirectoryDigestUtils; +import org.osgi.service.useradmin.User; + +/** + * A special user type used during authentication in order to provide the + * credentials required for scoping the user admin. + */ +public class AuthenticatingUser implements User { + /** From com.sun.security.auth.module.*LoginModule */ + public final static String SHARED_STATE_NAME = "javax.security.auth.login.name"; + /** From com.sun.security.auth.module.*LoginModule */ + public final static String SHARED_STATE_PWD = "javax.security.auth.login.password"; + + private final String name; + private final Dictionary 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 credentials) { + this.name = name; + this.credentials = credentials; + } + + public AuthenticatingUser(String name, char[] password) { + if (name == null) + throw new NullPointerException("Provided name cannot be null."); + this.name = name; + credentials = new Hashtable<>(); + credentials.put(SHARED_STATE_NAME, name); + byte[] pwd = DirectoryDigestUtils.charsToBytes(password); + credentials.put(SHARED_STATE_PWD, pwd); + } + + @Override + public String getName() { + return name; + } + + @Override + public int getType() { + return User.USER; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Dictionary getProperties() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Dictionary getCredentials() { + return credentials; + } + + @Override + public boolean hasCredential(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return "Authenticating user " + name; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java new file mode 100644 index 000000000..03f17e61f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java @@ -0,0 +1,402 @@ +package org.argeo.cms.osgi.useradmin; + +import static org.argeo.api.acr.ldap.LdapAttr.objectClass; +import static org.argeo.api.acr.ldap.LdapObj.extensibleObject; +import static org.argeo.api.acr.ldap.LdapObj.inetOrgPerson; +import static org.argeo.api.acr.ldap.LdapObj.organizationalPerson; +import static org.argeo.api.acr.ldap.LdapObj.person; +import static org.argeo.api.acr.ldap.LdapObj.top; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +import javax.naming.Context; +import javax.naming.InvalidNameException; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosTicket; + +import org.argeo.api.cms.directory.DirectoryDigestUtils; +import org.argeo.api.cms.directory.CmsUser; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.api.cms.directory.UserDirectory; +import org.argeo.cms.directory.ldap.AbstractLdapDirectory; +import org.argeo.cms.directory.ldap.LdapDao; +import org.argeo.cms.directory.ldap.LdapEntry; +import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy; +import org.argeo.cms.directory.ldap.LdapNameUtils; +import org.argeo.cms.directory.ldap.LdifDao; +import org.argeo.cms.runtime.DirectoryConf; +import org.argeo.cms.util.CurrentSubject; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** Base class for a {@link UserDirectory}. */ +public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdmin, UserDirectory { + + private UserAdmin externalRoles; + + // Transaction + public DirectoryUserAdmin(URI uriArg, Dictionary props) { + this(uriArg, props, false); + } + + public DirectoryUserAdmin(URI uriArg, Dictionary props, boolean scoped) { + super(uriArg, props, scoped); + } + + public DirectoryUserAdmin(Dictionary props) { + this(null, props); + } + + /* + * ABSTRACT METHODS + */ + + protected Optional scope(User user) { + if (getDirectoryDao() instanceof LdapDao) { + return scopeLdap(user); + } else if (getDirectoryDao() instanceof LdifDao) { + return scopeLdif(user); + } else { + throw new IllegalStateException("Unsupported DAO " + getDirectoryDao().getClass()); + } + } + + protected Optional scopeLdap(User user) { + Dictionary credentials = user.getCredentials(); + String username = (String) credentials.get(SHARED_STATE_USERNAME); + if (username == null) + username = user.getName(); + Dictionary properties = cloneConfigProperties(); + properties.put(Context.SECURITY_PRINCIPAL, username.toString()); + Object pwdCred = credentials.get(SHARED_STATE_PASSWORD); + byte[] pwd = (byte[]) pwdCred; + if (pwd != null) { + char[] password = DirectoryDigestUtils.bytesToChars(pwd); + properties.put(Context.SECURITY_CREDENTIALS, new String(password)); + } else { + properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); + } + DirectoryUserAdmin scopedDirectory = new DirectoryUserAdmin(null, properties, true); + scopedDirectory.init(); + // check connection + if (!scopedDirectory.getDirectoryDao().checkConnection()) + return Optional.empty(); + return Optional.of(scopedDirectory); + } + + protected Optional scopeLdif(User user) { + Dictionary credentials = user.getCredentials(); + String username = (String) credentials.get(SHARED_STATE_USERNAME); + if (username == null) + username = user.getName(); + Object pwdCred = credentials.get(SHARED_STATE_PASSWORD); + byte[] pwd = (byte[]) pwdCred; + if (pwd != null) { + char[] password = DirectoryDigestUtils.bytesToChars(pwd); + User directoryUser = (User) getRole(username); + if (!directoryUser.hasCredential(null, password)) + throw new IllegalStateException("Invalid credentials"); + } else { + throw new IllegalStateException("Password is required"); + } + Dictionary properties = cloneConfigProperties(); + properties.put(DirectoryConf.readOnly.name(), "true"); + DirectoryUserAdmin scopedUserAdmin = new DirectoryUserAdmin(null, properties, true); + // FIXME do it better + ((LdifDao) getDirectoryDao()).scope((LdifDao) scopedUserAdmin.getDirectoryDao()); + // no need to check authentication + scopedUserAdmin.init(); + return Optional.of(scopedUserAdmin); + } + + @Override + public String getRolePath(Role role) { + return nameToRelativePath(LdapNameUtils.toLdapName(role.getName())); + } + + @Override + public String getRoleSimpleName(Role role) { + LdapName dn = LdapNameUtils.toLdapName(role.getName()); + String name = LdapNameUtils.getLastRdnValue(dn); + return name; + } + + @Override + public Role getRoleByPath(String path) { + LdapEntry entry = doGetRole(pathToName(path)); + if (!(entry instanceof Role)) { + return null; +// throw new IllegalStateException("Path must be a UserAdmin Role."); + } else { + return (Role) entry; + } + } + + protected List getAllRoles(CmsUser user) { + List allRoles = new ArrayList(); + if (user != null) { + collectRoles((LdapEntry) user, allRoles); + allRoles.add(user); + } else + collectAnonymousRoles(allRoles); + return allRoles; + } + + private void collectRoles(LdapEntry user, List allRoles) { + List allEntries = new ArrayList<>(); + LdapEntry entry = user; + collectGroups(entry, allEntries); + for (LdapEntry e : allEntries) { + if (e instanceof Role) + allRoles.add((Role) e); + } + } + + private void collectAnonymousRoles(List allRoles) { + // TODO gather anonymous roles + } + + // USER ADMIN + @Override + public Role getRole(String name) { + return (Role) doGetRole(toLdapName(name)); + } + + @Override + public Role[] getRoles(String filter) throws InvalidSyntaxException { + List res = getRoles(getBaseDn(), filter, true); + return res.toArray(new Role[res.size()]); + } + + List getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException { + LdapEntryWorkingCopy wc = getWorkingCopy(); +// Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null; + List searchRes = getDirectoryDao().doGetEntries(searchBase, filter, deep); + List res = new ArrayList<>(); + for (LdapEntry entry : searchRes) + res.add((CmsUser) entry); + if (wc != null) { + for (Iterator it = res.iterator(); it.hasNext();) { + CmsUser user = (CmsUser) it.next(); + LdapName dn = LdapNameUtils.toLdapName(user.getName()); + if (wc.getDeletedData().containsKey(dn)) + it.remove(); + } + Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null; + for (LdapEntry ldapEntry : wc.getNewData().values()) { + CmsUser user = (CmsUser) ldapEntry; + if (f == null || f.match(user.getProperties())) + res.add(user); + } + // no need to check modified users, + // since doGetRoles was already based on the modified attributes + } + return res; + } + + @Override + public User getUser(String key, String value) { + // TODO check value null or empty + List collectedUsers = new ArrayList(); + if (key != null) { + doGetUser(key, value, collectedUsers); + } else { + throw new IllegalArgumentException("Key cannot be null"); + } + + if (collectedUsers.size() == 1) { + return collectedUsers.get(0); + } else if (collectedUsers.size() > 1) { + // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" : + // "") + value); + } + return null; + } + + protected void doGetUser(String key, String value, List collectedUsers) { + String f = "(" + key + "=" + value + ")"; + List users = getDirectoryDao().doGetEntries(getBaseDn(), f, true); + for (LdapEntry entry : users) + collectedUsers.add((CmsUser) entry); + } + + @Override + public Authorization getAuthorization(User user) { + if (user == null) {// anonymous + return new LdifAuthorization(user, getAllRoles(null)); + } + LdapName userName = toLdapName(user.getName()); + if (isExternal(userName) && user instanceof LdapEntry) { + List allRoles = new ArrayList(); + collectRoles((LdapEntry) user, allRoles); + return new LdifAuthorization(user, allRoles); + } else { + + Subject currentSubject = CurrentSubject.current(); + if (currentSubject != null // + && getRealm().isPresent() // + && !currentSubject.getPrivateCredentials(Authorization.class).isEmpty() // + && !currentSubject.getPrivateCredentials(KerberosTicket.class).isEmpty()) // + { + // TODO not only Kerberos but also bind scope with kept password ? + Authorization auth = currentSubject.getPrivateCredentials(Authorization.class).iterator().next(); + // bind with authenticating user + DirectoryUserAdmin scopedUserAdmin = CurrentSubject.callAs(currentSubject, () -> { + return scope(new AuthenticatingUser(auth.getName(), new Hashtable<>())).orElseThrow(); + }); + return getAuthorizationFromScoped(scopedUserAdmin, user); + } + + if (user instanceof CmsUser) { + return new LdifAuthorization(user, getAllRoles((CmsUser) user)); + } else { + // bind with authenticating user + DirectoryUserAdmin scopedUserAdmin = scope(user).orElseThrow(); + return getAuthorizationFromScoped(scopedUserAdmin, user); + } + } + } + + private Authorization getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin, User user) { + try { + CmsUser directoryUser = (CmsUser) scopedUserAdmin.getRole(user.getName()); + if (directoryUser == null) + throw new IllegalStateException("No scoped user found for " + user); + LdifAuthorization authorization = new LdifAuthorization(directoryUser, + scopedUserAdmin.getAllRoles(directoryUser)); + return authorization; + } finally { + scopedUserAdmin.destroy(); + } + } + + @Override + public Role createRole(String name, int type) { + checkEdit(); + LdapEntryWorkingCopy wc = getWorkingCopy(); + LdapName dn = toLdapName(name); + if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn)) + || wc.getNewData().containsKey(dn)) + throw new IllegalArgumentException("Already a role " + name); + BasicAttributes attrs = new BasicAttributes(true); + // attrs.put(LdifName.dn.name(), dn.toString()); + Rdn nameRdn = dn.getRdn(dn.size() - 1); + // TODO deal with multiple attr RDN + attrs.put(nameRdn.getType(), nameRdn.getValue()); + if (wc.getDeletedData().containsKey(dn)) { + wc.getDeletedData().remove(dn); + wc.getModifiedData().put(dn, attrs); + return getRole(name); + } else { + wc.getModifiedData().put(dn, attrs); + LdapEntry newRole = doCreateRole(dn, type, attrs); + wc.getNewData().put(dn, newRole); + return (Role) newRole; + } + } + + private LdapEntry doCreateRole(LdapName dn, int type, Attributes attrs) { + LdapEntry newRole; + BasicAttribute objClass = new BasicAttribute(objectClass.name()); + if (type == Role.USER) { + String userObjClass = getUserObjectClass(); + objClass.add(userObjClass); + if (inetOrgPerson.name().equals(userObjClass)) { + objClass.add(organizationalPerson.name()); + objClass.add(person.name()); + } else if (organizationalPerson.name().equals(userObjClass)) { + objClass.add(person.name()); + } + objClass.add(top.name()); + objClass.add(extensibleObject.name()); + attrs.put(objClass); + newRole = newUser(dn); + } else if (type == Role.GROUP) { + String groupObjClass = getGroupObjectClass(); + objClass.add(groupObjClass); + // objClass.add(LdifName.extensibleObject.name()); + objClass.add(top.name()); + attrs.put(objClass); + newRole = newGroup(dn); + } else + throw new IllegalArgumentException("Unsupported type " + type); + return newRole; + } + + @Override + public boolean removeRole(String name) { + return removeEntry(LdapNameUtils.toLdapName(name)); + } + + /* + * HIERARCHY + */ + @Override + public HierarchyUnit getHierarchyUnit(Role role) { + LdapName dn = LdapNameUtils.toLdapName(role.getName()); + LdapName huDn = LdapNameUtils.getParent(dn); + HierarchyUnit hierarchyUnit = getDirectoryDao().doGetHierarchyUnit(huDn); + if (hierarchyUnit == null) + throw new IllegalStateException("No hierarchy unit found for " + role); + return hierarchyUnit; + } + + @Override + public Iterable getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) { + LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getBase()); + try { + return getRoles(dn, filter, deep); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e); + } + } + + /* + * ROLES CREATION + */ + protected LdapEntry newUser(LdapName name) { + // TODO support devices, applications, etc. + return new LdifUser(this, name); + } + + protected LdapEntry newGroup(LdapName name) { + return new LdifGroup(this, name); + + } + + // GETTERS + protected UserAdmin getExternalRoles() { + return externalRoles; + } + + public void setExternalRoles(UserAdmin externalRoles) { + this.externalRoles = externalRoles; + } + + /* + * STATIC UTILITIES + */ + static LdapName toLdapName(String name) { + try { + return new LdapName(name); + } catch (InvalidNameException e) { + throw new IllegalArgumentException(name + " is not an LDAP name", e); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java new file mode 100644 index 000000000..a54050bc6 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java @@ -0,0 +1,85 @@ +package org.argeo.cms.osgi.useradmin; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Dictionary; +import java.util.List; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +/** Basic authorization. */ +class LdifAuthorization implements Authorization { + private final String name; + private final String displayName; + private final List allRoles; + + public LdifAuthorization(User user, List 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 props = user.getProperties(); + Object displayName = props.get(LdapAttr.displayName.name()); + if (displayName == null) + displayName = props.get(LdapAttr.cn.name()); + if (displayName == null) + displayName = props.get(LdapAttr.uid.name()); + if (displayName == null) + displayName = user.getName(); + if (displayName == null) + throw new IllegalStateException("Cannot set display name for " + user); + return displayName.toString(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java new file mode 100644 index 000000000..99aca1f2f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java @@ -0,0 +1,128 @@ +package org.argeo.cms.osgi.useradmin; + +import java.util.ArrayList; +import java.util.List; + +import javax.naming.InvalidNameException; +import javax.naming.directory.Attribute; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.directory.CmsGroup; +import org.argeo.cms.directory.ldap.AbstractLdapDirectory; +import org.osgi.service.useradmin.Role; + +/** Directory group implementation */ +class LdifGroup extends LdifUser implements CmsGroup { + private final String memberAttributeId; + + LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn) { + super(userAdmin, dn); + memberAttributeId = userAdmin.getMemberAttributeId(); + } + + @Override + public boolean addMember(Role role) { + try { + Role foundRole = findRole(new LdapName(role.getName())); + if (foundRole == null) + throw new UnsupportedOperationException( + "Adding role " + role.getName() + " is unsupported within this context."); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted"); + } + + getUserAdmin().checkEdit(); + if (!isEditing()) + startEditing(); + + Attribute member = getAttributes().get(memberAttributeId); + if (member != null) { + if (member.contains(role.getName())) + return false; + else + member.add(role.getName()); + } else + getAttributes().put(memberAttributeId, role.getName()); + return true; + } + + @Override + public boolean addRequiredMember(Role role) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeMember(Role role) { + getUserAdmin().checkEdit(); + if (!isEditing()) + startEditing(); + + Attribute member = getAttributes().get(memberAttributeId); + if (member != null) { + if (!member.contains(role.getName())) + return false; + member.remove(role.getName()); + return true; + } else + return false; + } + + @Override + public Role[] getMembers() { + List directMembers = new ArrayList(); + for (LdapName ldapName : getReferences(memberAttributeId)) { + Role role = findRole(ldapName); + if (role == null) { + throw new IllegalStateException("Role " + ldapName + " not found."); + } + directMembers.add(role); + } + return directMembers.toArray(new Role[directMembers.size()]); + } + + /** + * Whether a role with this name can be found from this context. + * + * @return The related {@link Role} or null. + */ + 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 getMemberNames() { +// Attribute memberAttribute = getAttributes().get(memberAttributeId); +// if (memberAttribute == null) +// return new ArrayList(); +// try { +// List roles = new ArrayList(); +// NamingEnumeration values = memberAttribute.getAll(); +// while (values.hasMore()) { +// LdapName dn = new LdapName(values.next().toString()); +// roles.add(dn); +// } +// return roles; +// } catch (NamingException e) { +// throw new IllegalStateException("Cannot get members", e); +// } +// } + + @Override + public Role[] getRequiredMembers() { + throw new UnsupportedOperationException(); + } + + @Override + public int getType() { + return GROUP; + } + + protected DirectoryUserAdmin getUserAdmin() { + return (DirectoryUserAdmin) getDirectory(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java new file mode 100644 index 000000000..e48869a01 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java @@ -0,0 +1,25 @@ +package org.argeo.cms.osgi.useradmin; + +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.directory.CmsUser; +import org.argeo.cms.directory.ldap.AbstractLdapDirectory; +import org.argeo.cms.directory.ldap.DefaultLdapEntry; + +/** Directory user implementation */ +class LdifUser extends DefaultLdapEntry implements CmsUser { + LdifUser(AbstractLdapDirectory userAdmin, LdapName dn) { + super(userAdmin, dn); + } + + @Override + public String getName() { + return getDn().toString(); + } + + @Override + public int getType() { + return USER; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserDirectory.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserDirectory.java new file mode 100644 index 000000000..41277d391 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserDirectory.java @@ -0,0 +1,111 @@ +package org.argeo.cms.osgi.useradmin; + +import java.util.ArrayList; +import java.util.List; + +import javax.naming.NameNotFoundException; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapName; + +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.cms.directory.HierarchyUnit; +import org.argeo.cms.directory.ldap.AbstractLdapDirectory; +import org.argeo.cms.directory.ldap.AbstractLdapDirectoryDao; +import org.argeo.cms.directory.ldap.LdapEntry; +import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy; + +/** Pseudo user directory to be used when logging in as OS user. */ +public class OsUserDirectory extends AbstractLdapDirectoryDao { + private final String osUsername = System.getProperty("user.name"); + private final LdapName osUserDn; + private final LdapEntry osUser; + + public OsUserDirectory(AbstractLdapDirectory directory) { + super(directory); + try { + osUserDn = new LdapName(LdapAttr.uid.name() + "=" + osUsername + "," + directory.getUserBaseRdn() + "," + + directory.getBaseDn()); +// Attributes attributes = new BasicAttributes(); +// attributes.put(LdapAttrs.uid.name(), osUsername); + osUser = newUser(osUserDn); + } catch (NamingException e) { + throw new IllegalStateException("Cannot create system user", e); + } + } + + @Override + public List getDirectGroups(LdapName dn) { + return new ArrayList<>(); + } + + @Override + public boolean entryExists(LdapName dn) { + return osUserDn.equals(dn); + } + + @Override + public boolean checkConnection() { + return true; + } + + @Override + public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException { + if (osUserDn.equals(key)) + return osUser; + else + throw new NameNotFoundException("Not an OS role"); + } + + @Override + public List doGetEntries(LdapName searchBase, String f, boolean deep) { + List res = new ArrayList<>(); +// if (f == null || f.match(osUser.getProperties())) + res.add(osUser); + return res; + } + + @Override + public HierarchyUnit doGetHierarchyUnit(LdapName dn) { + return null; + } + + @Override + public Iterable doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) { + return new ArrayList<>(); + } + + public void prepare(LdapEntryWorkingCopy wc) { + + } + + public void commit(LdapEntryWorkingCopy wc) { + + } + + public void rollback(LdapEntryWorkingCopy wc) { + + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + + @Override + public void destroy() { + // TODO Auto-generated method stub + + } + + @Override + public Attributes doGetAttributes(LdapName name) { + try { + return doGetEntry(name).getAttributes(); + } catch (NameNotFoundException e) { + throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn(), e); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java new file mode 100644 index 000000000..f71878060 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java @@ -0,0 +1,54 @@ +package org.argeo.cms.osgi.useradmin; + +import java.net.URISyntaxException; +import java.net.URL; +import java.security.NoSuchAlgorithmException; +import java.security.URIParameter; + +import javax.security.auth.Subject; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +/** Log in based on JDK-provided OS integration. */ +public class OsUserUtils { + private final static String LOGIN_CONTEXT_USER_NIX = "USER_NIX"; + private final static String LOGIN_CONTEXT_USER_NT = "USER_NT"; + + public static String getOsUsername() { + return System.getProperty("user.name"); + } + + public static LoginContext loginAsSystemUser(Subject subject) { + try { + URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader() + .getResource("org/argeo/osgi/useradmin/jaas-os.cfg"); + URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI()); + Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter); + LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject, + null, jaasConfiguration); + lc.login(); + return lc; + } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) { + throw new RuntimeException("Cannot login as system user", e); + } + } + + public static void main(String args[]) { + Subject subject = new Subject(); + LoginContext loginContext = loginAsSystemUser(subject); + System.out.println(subject); + try { + loginContext.logout(); + } catch (LoginException e) { + // silent + } + } + + private static boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); + } + + private OsUserUtils() { + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/TokenUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/TokenUtils.java new file mode 100644 index 000000000..241f6092d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/TokenUtils.java @@ -0,0 +1,87 @@ +package org.argeo.cms.osgi.useradmin; + +import static org.argeo.api.acr.ldap.LdapAttr.description; +import static org.argeo.api.acr.ldap.LdapAttr.owner; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashSet; +import java.util.Set; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.security.auth.Subject; + +import org.argeo.api.acr.ldap.NamingUtils; +import org.osgi.service.useradmin.Group; + +/** + * Canonically implements the Argeo token conventions. + */ +public class TokenUtils { + public static Set tokensUsed(Subject subject, String tokensBaseDn) { + Set res = new HashSet<>(); + for (Principal principal : subject.getPrincipals()) { + String name = principal.getName(); + if (name.endsWith(tokensBaseDn)) { + try { + LdapName ldapName = new LdapName(name); + String token = ldapName.getRdn(ldapName.size()).getValue().toString(); + res.add(token); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Invalid principal " + principal, e); + } + } + } + return res; + } + + /** The user related to this token group */ + public static String userDn(Group tokenGroup) { + return (String) tokenGroup.getProperties().get(owner.name()); + } + + public static boolean isExpired(Group tokenGroup) { + return isExpired(tokenGroup, Instant.now()); + + } + + public static boolean isExpired(Group tokenGroup, Instant instant) { + String expiryDateStr = (String) tokenGroup.getProperties().get(description.name()); + if (expiryDateStr != null) { + Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr); + if (expiryDate.isBefore(instant)) { + return true; + } + } + return false; + } + +// private final String token; +// +// public TokenUtils(String token) { +// this.token = token; +// } +// +// public String getToken() { +// return token; +// } +// +// @Override +// public int hashCode() { +// return token.hashCode(); +// } +// +// @Override +// public boolean equals(Object obj) { +// if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token)) +// return true; +// return false; +// } +// +// @Override +// public String toString() { +// return "Token #" + hashCode(); +// } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/jaas-os.cfg b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/jaas-os.cfg new file mode 100644 index 000000000..da04505a7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/jaas-os.cfg @@ -0,0 +1,8 @@ +USER_NIX { + com.sun.security.auth.module.UnixLoginModule requisite; +}; + +USER_NT { + com.sun.security.auth.module.NTLoginModule requisite; +}; + diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/package-info.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/package-info.java new file mode 100644 index 000000000..766c59b3e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/package-info.java @@ -0,0 +1,2 @@ +/** LDAP and LDIF based OSGi useradmin implementation. */ +package org.argeo.cms.osgi.useradmin; \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/DirectoryConf.java b/org.argeo.cms/src/org/argeo/cms/runtime/DirectoryConf.java new file mode 100644 index 000000000..a4e44ccaf --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/runtime/DirectoryConf.java @@ -0,0 +1,247 @@ +package org.argeo.cms.runtime; + +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.argeo.api.acr.ldap.NamingUtils; +import org.argeo.api.cms.directory.DirectoryDigestUtils; +import org.argeo.cms.directory.ldap.IpaUtils; + +/** Properties used to configure user admins. */ +public enum DirectoryConf { + /** Base DN (cannot be configured externally) */ + baseDn(null), + + /** URI of the underlying resource (cannot be configured externally) */ + uri(null), + + /** User objectClass */ + userObjectClass("inetOrgPerson"), + + /** Relative base DN for users */ + userBase("ou=People"), + + /** Groups objectClass */ + groupObjectClass("groupOfNames"), + + /** Relative base DN for users */ + groupBase("ou=Groups"), + + /** Relative base DN for users */ + systemRoleBase("ou=Roles"), + + /** Read-only source */ + readOnly(null), + + /** Disabled source */ + disabled(null), + + /** Authentication realm */ + realm(null), + + /** Override all passwords with this value (typically for testing purposes) */ + forcedPassword(null); + + public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config"; + + public final static String SCHEME_LDAP = "ldap"; + public final static String SCHEME_LDAPS = "ldaps"; + public final static String SCHEME_FILE = "file"; + public final static String SCHEME_OS = "os"; + public final static String SCHEME_IPA = "ipa"; + + private final static String SECURITY_PRINCIPAL = "java.naming.security.principal"; + private final static String SECURITY_CREDENTIALS = "java.naming.security.credentials"; + + /** The default value. */ + private Object def; + + DirectoryConf(Object def) { + this.def = def; + } + + public Object getDefault() { + return def; + } + + /** + * For use as Java property. + * + * @deprecated use {@link #name()} instead + */ + @Deprecated + public String property() { + return name(); + } + + public String getValue(Dictionary properties) { + Object res = getRawValue(properties); + if (res == null) + return null; + return res.toString(); + } + + @SuppressWarnings("unchecked") + public T getRawValue(Dictionary properties) { + Object res = properties.get(name()); + if (res == null) + res = getDefault(); + return (T) res; + } + + /** @deprecated use {@link #valueOf(String)} instead */ + @Deprecated + public static DirectoryConf local(String property) { + return DirectoryConf.valueOf(property); + } + + /** Hides host and credentials. */ + public static URI propertiesAsUri(Dictionary properties) { + StringBuilder query = new StringBuilder(); + + boolean first = true; +// for (Enumeration keys = properties.keys(); keys.hasMoreElements();) { +// String key = keys.nextElement(); +// // TODO clarify which keys are relevant (list only the enum?) +// if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn") +// && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name()) +// && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS) +// && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) { +// if (first) +// first = false; +// else +// query.append('&'); +// query.append(valueOf(key).name()); +// query.append('=').append(properties.get(key).toString()); +// } +// } + + keys: for (DirectoryConf key : DirectoryConf.values()) { + if (key.equals(baseDn) || key.equals(uri)) + continue keys; + Object value = properties.get(key.name()); + if (value == null) + continue keys; + if (first) + first = false; + else + query.append('&'); + query.append(key.name()); + query.append('=').append(value.toString()); + + } + + Object bDnObj = properties.get(baseDn.name()); + String bDn = bDnObj != null ? bDnObj.toString() : null; + try { + return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null, + null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot create URI from properties", e); + } + } + + public static Dictionary uriAsProperties(String uriStr) { + try { + Hashtable res = new Hashtable(); + URI u = new URI(uriStr); + String scheme = u.getScheme(); + if (scheme != null && scheme.equals(SCHEME_IPA)) { + return IpaUtils.convertIpaUri(u); +// scheme = u.getScheme(); + } + String path = u.getPath(); + // base DN + String bDn = path.substring(path.lastIndexOf('/') + 1, path.length()); + if (bDn.equals("") && SCHEME_OS.equals(scheme)) { + bDn = getBaseDnFromHostname(); + } + + if (bDn.endsWith(".ldif")) + bDn = bDn.substring(0, bDn.length() - ".ldif".length()); + + // Normalize base DN as LDAP name +// bDn = new LdapName(bDn).toString(); + + String principal = null; + String credentials = null; + if (scheme != null) + if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) { + // TODO additional checks + if (u.getUserInfo() != null) { + String[] userInfo = u.getUserInfo().split(":"); + principal = userInfo.length > 0 ? userInfo[0] : null; + credentials = userInfo.length > 1 ? userInfo[1] : null; + } + } else if (scheme.equals(SCHEME_FILE)) { + } else if (scheme.equals(SCHEME_IPA)) { + } else if (scheme.equals(SCHEME_OS)) { + } else + throw new IllegalArgumentException("Unsupported scheme " + scheme); + Map> query = NamingUtils.queryToMap(u); + for (String key : query.keySet()) { + DirectoryConf ldapProp = DirectoryConf.valueOf(key); + List values = query.get(key); + if (values.size() == 1) { + res.put(ldapProp.name(), values.get(0)); + } else { + throw new IllegalArgumentException("Only single values are supported"); + } + } + res.put(baseDn.name(), bDn); + if (SCHEME_OS.equals(scheme)) + res.put(readOnly.name(), "true"); + if (principal != null) + res.put(SECURITY_PRINCIPAL, principal); + if (credentials != null) + res.put(SECURITY_CREDENTIALS, credentials); + if (scheme != null) {// relative URIs are dealt with externally + if (SCHEME_OS.equals(scheme)) { + res.put(uri.name(), SCHEME_OS + ":///"); + } else { + URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(), + scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null); + res.put(uri.name(), bareUri.toString()); + } + } + return res; + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot convert " + uri + " to properties", e); + } + } + + private static String getBaseDnFromHostname() { + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + hostname = "localhost.localdomain"; + } + int dotIdx = hostname.indexOf('.'); + if (dotIdx >= 0) { + String domain = hostname.substring(dotIdx + 1, hostname.length()); + String bDn = ("." + domain).replaceAll("\\.", ",dc="); + bDn = bDn.substring(1, bDn.length()); + return bDn; + } else { + return "dc=" + hostname; + } + } + + /** + * Hash the base DN in order to have a deterministic string to be used as a cn + * for the underlying user directory. + */ + public static String baseDnHash(Dictionary properties) { + String bDn = (String) properties.get(baseDn.name()); + if (bDn == null) + throw new IllegalStateException("No baseDn in " + properties); + return DirectoryDigestUtils.sha1str(bDn); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java new file mode 100644 index 000000000..76775fed8 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java @@ -0,0 +1,169 @@ +package org.argeo.cms.runtime; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; + +import org.argeo.api.acr.ContentRepository; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsState; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.api.cms.transaction.SimpleTransactionManager; +import org.argeo.api.cms.transaction.WorkControl; +import org.argeo.api.cms.transaction.WorkTransaction; +import org.argeo.api.register.Component; +import org.argeo.api.register.ComponentRegister; +import org.argeo.api.register.SimpleRegister; +import org.argeo.api.uuid.UuidFactory; +import org.argeo.cms.acr.CmsUuidFactory; +import org.argeo.cms.internal.runtime.CmsContextImpl; +import org.argeo.cms.internal.runtime.CmsDeploymentImpl; +import org.argeo.cms.internal.runtime.CmsStateImpl; +import org.argeo.cms.internal.runtime.CmsUserAdmin; +import org.argeo.cms.internal.runtime.CmsUserManagerImpl; +import org.argeo.cms.internal.runtime.DeployedContentRepository; +import org.osgi.service.useradmin.UserAdmin; + +/** + * A CMS assembly which is programmatically defined, as an alternative to OSGi + * deployment. Useful for testing or AOT compilation. + */ +public class StaticCms { + private SimpleRegister register = new SimpleRegister(); + + private CompletableFuture stopped = new CompletableFuture(); + + public void start() { + // UID factory + CmsUuidFactory uuidFactory = new CmsUuidFactory(); + Component uuidFactoryC = new Component.Builder<>(uuidFactory) // + .addType(UuidFactory.class) // + .build(register); + + // CMS State + CmsStateImpl cmsState = new CmsStateImpl(); + Component cmsStateC = new Component.Builder<>(cmsState) // + .addType(CmsState.class) // + .addActivation(cmsState::start) // + .addDeactivation(cmsState::stop) // + .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsState::setUuidFactory, null) // + .build(register); + + // Transaction manager + SimpleTransactionManager transactionManager = new SimpleTransactionManager(); + Component transactionManagerC = new Component.Builder<>(transactionManager) // + .addType(WorkControl.class) // + .addType(WorkTransaction.class) // + .build(register); + + // User Admin + CmsUserAdmin userAdmin = new CmsUserAdmin(); + Component userAdminC = new Component.Builder<>(userAdmin) // + .addType(UserAdmin.class) // + .addActivation(userAdmin::start) // + .addDeactivation(userAdmin::stop) // + .addDependency(cmsStateC.getType(CmsState.class), userAdmin::setCmsState, null) // + .addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) // + .addDependency(transactionManagerC.getType(WorkTransaction.class), userAdmin::setUserTransaction, null) // + .build(register); + + // User manager + CmsUserManagerImpl userManager = new CmsUserManagerImpl(); +// for (UserDirectory userDirectory : userAdmin.getUserDirectories()) { +// // FIXME deal with properties +// userManager.addUserDirectory(userDirectory, new HashMap<>()); +// } + Component userManagerC = new Component.Builder<>(userManager) // + .addType(CmsUserManager.class) // + .addActivation(userManager::start) // + .addDeactivation(userManager::stop) // + .addDependency(userAdminC.getType(UserAdmin.class), userManager::setUserAdmin, null) // + .addDependency(transactionManagerC.getType(WorkTransaction.class), userManager::setUserTransaction, + null) // + .build(register); + + // Content Repository + DeployedContentRepository contentRepository = new DeployedContentRepository(); + Component contentRepositoryC = new Component.Builder<>(contentRepository) // + .addType(ProvidedRepository.class) // + .addType(ContentRepository.class) // + .addActivation(contentRepository::start) // + .addDeactivation(contentRepository::stop) // + .addDependency(cmsStateC.getType(CmsState.class), contentRepository::setCmsState, null) // + .addDependency(uuidFactoryC.getType(UuidFactory.class), contentRepository::setUuidFactory, null) // + .addDependency(userManagerC.getType(CmsUserManager.class), contentRepository::setUserManager, null) // + .build(register); + + // CMS Deployment + CmsDeploymentImpl cmsDeployment = new CmsDeploymentImpl(); + Component cmsDeploymentC = new Component.Builder<>(cmsDeployment) // + .addType(CmsDeployment.class) // + .addActivation(cmsDeployment::start) // + .addDeactivation(cmsDeployment::stop) // + .addDependency(cmsStateC.getType(CmsState.class), cmsDeployment::setCmsState, null) // +// .addDependency(deployConfigC.getType(DeployConfig.class), cmsDeployment::setDeployConfig, null) // + .build(register); + + // CMS Context + CmsContextImpl cmsContext = new CmsContextImpl(); + Component cmsContextC = new Component.Builder<>(cmsContext) // + .addType(CmsContext.class) // + .addActivation(cmsContext::start) // + .addDeactivation(cmsContext::stop) // + .addDependency(cmsStateC.getType(CmsState.class), cmsContext::setCmsState, null) // + .addDependency(cmsDeploymentC.getType(CmsDeployment.class), cmsContext::setCmsDeployment, null) // + .addDependency(userAdminC.getType(UserAdmin.class), cmsContext::setUserAdmin, null) // + .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsContext::setUuidFactory, null) // +// .addDependency(contentRepositoryC.getType(ProvidedRepository.class), cmsContext::setContentRepository, +// null) // + .build(register); + assert cmsContextC.get() == cmsContext; + + addComponents(register); + + register.activate(); + + postActivation(register); + } + + protected void addComponents(ComponentRegister register) { + + } + + protected void postActivation(ComponentRegister register) { + + } + + public ComponentRegister getComponentRegister() { + return register; + } + + public void stop() { + if (register.isActive()) { + register.deactivate(); + } + register.clear(); + stopped.complete(null); + } + + public void waitForStop() { + stopped.join(); + } + + public static void main(String[] args) { + if (args.length == 0) { + System.err.println("Usage: "); + System.exit(1); + } + Path instancePath = Paths.get(args[0]); + System.setProperty("osgi.instance.area", instancePath.toUri().toString()); + + StaticCms staticCms = new StaticCms(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown")); + staticCms.start(); + staticCms.waitForStop(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java deleted file mode 100644 index 08ac54936..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.argeo.cms.security; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.CharArrayWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; -import java.security.AccessController; -import java.security.Provider; -import java.security.Security; -import java.util.Arrays; -import java.util.Iterator; - -import javax.crypto.SecretKey; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.commons.io.IOUtils; -import org.argeo.api.cms.CmsAuth; -import org.argeo.cms.CmsException; - -/** username / password based keyring. TODO internationalize */ -public abstract class AbstractKeyring implements Keyring, CryptoKeyring { - // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING"; - - // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT; - private CallbackHandler defaultCallbackHandler; - - private String charset = "UTF-8"; - - /** - * Default provider is bouncy castle, in order to have consistent behaviour - * across implementations - */ - private String securityProviderName = "BC"; - - /** - * Whether the keyring has already been created in the past with a master - * password - */ - protected abstract Boolean isSetup(); - - /** - * Setup the keyring persistently, {@link #isSetup()} must return true - * afterwards - */ - protected abstract void setup(char[] password); - - /** Populates the key spec callback */ - protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback); - - protected abstract void encrypt(String path, InputStream unencrypted); - - protected abstract InputStream decrypt(String path); - - /** Triggers lazy initialization */ - protected SecretKey getSecretKey(char[] password) { - Subject subject = Subject.getSubject(AccessController.getContext()); - // we assume only one secrete key is available - Iterator iterator = subject.getPrivateCredentials(SecretKey.class).iterator(); - if (!iterator.hasNext() || password!=null) {// not initialized - CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler() - : new PasswordProvidedCallBackHandler(password); - ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - try { - LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject, - callbackHandler); - loginContext.login(); - // FIXME will login even if password is wrong - iterator = subject.getPrivateCredentials(SecretKey.class).iterator(); - return iterator.next(); - } catch (LoginException e) { - throw new CmsException("Keyring login failed", e); - } finally { - Thread.currentThread().setContextClassLoader(currentContextClassLoader); - } - - } else { - SecretKey secretKey = iterator.next(); - if (iterator.hasNext()) - throw new CmsException("More than one secret key in private credentials"); - return secretKey; - } - } - - public InputStream getAsStream(String path) { - return decrypt(path); - } - - public void set(String path, InputStream in) { - encrypt(path, in); - } - - public char[] getAsChars(String path) { - // InputStream in = getAsStream(path); - // CharArrayWriter writer = null; - // Reader reader = null; - try (InputStream in = getAsStream(path); - CharArrayWriter writer = new CharArrayWriter(); - Reader reader = new InputStreamReader(in, charset);) { - IOUtils.copy(reader, writer); - return writer.toCharArray(); - } catch (IOException e) { - throw new CmsException("Cannot decrypt to char array", e); - } finally { - // IOUtils.closeQuietly(reader); - // IOUtils.closeQuietly(in); - // IOUtils.closeQuietly(writer); - } - } - - public void set(String path, char[] arr) { - // ByteArrayOutputStream out = new ByteArrayOutputStream(); - // ByteArrayInputStream in = null; - // Writer writer = null; - try (ByteArrayOutputStream out = new ByteArrayOutputStream(); - Writer writer = new OutputStreamWriter(out, charset);) { - // writer = new OutputStreamWriter(out, charset); - writer.write(arr); - writer.flush(); - // in = new ByteArrayInputStream(out.toByteArray()); - try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) { - set(path, in); - } - } catch (IOException e) { - throw new CmsException("Cannot encrypt to char array", e); - } finally { - // IOUtils.closeQuietly(writer); - // IOUtils.closeQuietly(out); - // IOUtils.closeQuietly(in); - } - } - - public void unlock(char[] password) { - if (!isSetup()) - setup(password); - SecretKey secretKey = getSecretKey(password); - if (secretKey == null) - throw new CmsException("Could not unlock keyring"); - } - - protected Provider getSecurityProvider() { - return Security.getProvider(securityProviderName); - } - - public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) { - this.defaultCallbackHandler = defaultCallbackHandler; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public void setSecurityProviderName(String securityProviderName) { - this.securityProviderName = securityProviderName; - } - - // @Deprecated - // protected static byte[] hash(char[] password, byte[] salt, Integer - // iterationCount) { - // ByteArrayOutputStream out = null; - // OutputStreamWriter writer = null; - // try { - // out = new ByteArrayOutputStream(); - // writer = new OutputStreamWriter(out, "UTF-8"); - // writer.write(password); - // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256"); - // pwDigest.reset(); - // pwDigest.update(salt); - // byte[] btPass = pwDigest.digest(out.toByteArray()); - // for (int i = 0; i < iterationCount; i++) { - // pwDigest.reset(); - // btPass = pwDigest.digest(btPass); - // } - // return btPass; - // } catch (Exception e) { - // throw new CmsException("Cannot hash", e); - // } finally { - // IOUtils.closeQuietly(out); - // IOUtils.closeQuietly(writer); - // } - // - // } - - /** - * Convenience method using the underlying callback to ask for a password - * (typically used when the password is not saved in the keyring) - */ - protected char[] ask() { - PasswordCallback passwordCb = new PasswordCallback("Password", false); - Callback[] dialogCbs = new Callback[] { passwordCb }; - try { - defaultCallbackHandler.handle(dialogCbs); - char[] password = passwordCb.getPassword(); - return password; - } catch (Exception e) { - throw new CmsException("Cannot ask for a password", e); - } - - } - - class KeyringCallbackHandler implements CallbackHandler { - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - // checks - if (callbacks.length != 2) - throw new IllegalArgumentException( - "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}"); - if (!(callbacks[0] instanceof PasswordCallback)) - throw new UnsupportedCallbackException(callbacks[0]); - if (!(callbacks[1] instanceof PBEKeySpecCallback)) - throw new UnsupportedCallbackException(callbacks[0]); - - PasswordCallback passwordCb = (PasswordCallback) callbacks[0]; - PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1]; - - if (isSetup()) { - Callback[] dialogCbs = new Callback[] { passwordCb }; - defaultCallbackHandler.handle(dialogCbs); - } else {// setup keyring - TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION, - "Enter a master password which will protect your private data"); - TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION, - "(for example your credentials to third-party services)"); - TextOutputCallback textCb3 = new TextOutputCallback(TextOutputCallback.INFORMATION, - "Don't forget this password since the data cannot be read without it"); - PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false); - // first try - Callback[] dialogCbs = new Callback[] { textCb1, textCb2, textCb3, passwordCb, confirmPasswordCb }; - defaultCallbackHandler.handle(dialogCbs); - - // if passwords different, retry (except if cancelled) - while (passwordCb.getPassword() != null - && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) { - TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR, - "The passwords do not match"); - dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb }; - defaultCallbackHandler.handle(dialogCbs); - } - - if (passwordCb.getPassword() != null) {// not cancelled - setup(passwordCb.getPassword()); - } - } - - if (passwordCb.getPassword() != null) - handleKeySpecCallback(pbeCb); - } - - } - - class PasswordProvidedCallBackHandler implements CallbackHandler { - private final char[] password; - - public PasswordProvidedCallBackHandler(char[] password) { - this.password = password; - } - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - // checks - if (callbacks.length != 2) - throw new IllegalArgumentException( - "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}"); - if (!(callbacks[0] instanceof PasswordCallback)) - throw new UnsupportedCallbackException(callbacks[0]); - if (!(callbacks[1] instanceof PBEKeySpecCallback)) - throw new UnsupportedCallbackException(callbacks[0]); - - PasswordCallback passwordCb = (PasswordCallback) callbacks[0]; - passwordCb.setPassword(password); - PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1]; - handleKeySpecCallback(pbeCb); - } - - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java b/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java deleted file mode 100644 index 69e8a08fd..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.argeo.cms.security; - -import java.io.IOException; -import java.math.BigInteger; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.security.MessageDigest; -import java.util.Base64; -import java.util.zip.Checksum; - -import org.argeo.cms.CmsException; - -/** Allows to fine tune how files are read. */ -public class ChecksumFactory { - private int regionSize = 10 * 1024 * 1024; - - public byte[] digest(Path path, final String algo) { - try { - final MessageDigest md = MessageDigest.getInstance(algo); - if (Files.isDirectory(path)) { - long begin = System.currentTimeMillis(); - Files.walkFileTree(path, new SimpleFileVisitor() { - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (!Files.isDirectory(file)) { - byte[] digest = digest(file, algo); - md.update(digest); - } - return FileVisitResult.CONTINUE; - } - - }); - byte[] digest = md.digest(); - long duration = System.currentTimeMillis() - begin; - System.out.println(printBase64Binary(digest) + " " + path + " (" + duration / 1000 + "s)"); - return digest; - } else { - long begin = System.nanoTime(); - long length = -1; - try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) { - length = channel.size(); - long cursor = 0; - while (cursor < length) { - long effectiveSize = Math.min(regionSize, length - cursor); - MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize); - // md.update(mb); - byte[] buffer = new byte[1024]; - while (mb.hasRemaining()) { - mb.get(buffer); - md.update(buffer); - } - - // sub digest - // mb.flip(); - // MessageDigest subMd = - // MessageDigest.getInstance(algo); - // subMd.update(mb); - // byte[] subDigest = subMd.digest(); - // System.out.println(" -> " + cursor); - // System.out.println(IOUtils.encodeHexString(subDigest)); - // System.out.println(new BigInteger(1, - // subDigest).toString(16)); - // System.out.println(new BigInteger(1, subDigest) - // .toString(Character.MAX_RADIX)); - // System.out.println(printBase64Binary(subDigest)); - - cursor = cursor + regionSize; - } - byte[] digest = md.digest(); - long duration = System.nanoTime() - begin; - System.out.println(printBase64Binary(digest) + " " + path.getFileName() + " (" + duration / 1000000 - + "ms, " + (length / 1024) + "kB, " + (length / (duration / 1000000)) * 1000 / (1024 * 1024) - + " MB/s)"); - return digest; - } - } - } catch (Exception e) { - throw new CmsException("Cannot digest " + path, e); - } - } - - /** Whether the file should be mapped. */ - protected boolean mapFile(FileChannel fileChannel) throws IOException { - long size = fileChannel.size(); - if (size > (regionSize / 10)) - return true; - return false; - } - - public long checksum(Path path, Checksum crc) { - final int bufferSize = 2 * 1024 * 1024; - long begin = System.currentTimeMillis(); - try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) { - byte[] bytes = new byte[bufferSize]; - long length = channel.size(); - long cursor = 0; - while (cursor < length) { - long effectiveSize = Math.min(regionSize, length - cursor); - MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize); - int nGet; - while (mb.hasRemaining()) { - nGet = Math.min(mb.remaining(), bufferSize); - mb.get(bytes, 0, nGet); - crc.update(bytes, 0, nGet); - } - cursor = cursor + regionSize; - } - return crc.getValue(); - } catch (Exception e) { - throw new CmsException("Cannot checksum " + path, e); - } finally { - long duration = System.currentTimeMillis() - begin; - System.out.println(duration / 1000 + "s"); - } - } - - public static void main(String... args) { - ChecksumFactory cf = new ChecksumFactory(); - // Path path = - // Paths.get("/home/mbaudier/apache-maven-3.2.3-bin.tar.gz"); - Path path; - if (args.length > 0) { - path = Paths.get(args[0]); - } else { - path = Paths.get("/home/mbaudier/Downloads/torrents/CentOS-7-x86_64-DVD-1503-01/" - + "CentOS-7-x86_64-DVD-1503-01.iso"); - } - // long adler = cf.checksum(path, new Adler32()); - // System.out.format("Adler=%d%n", adler); - // long crc = cf.checksum(path, new CRC32()); - // System.out.format("CRC=%d%n", crc); - String algo = "SHA1"; - byte[] digest = cf.digest(path, algo); - System.out.println(algo + " " + printBase64Binary(digest)); - System.out.println(algo + " " + new BigInteger(1, digest).toString(16)); - // String sha1 = printBase64Binary(cf.digest(path, "SHA1")); - // System.out.format("SHA1=%s%n", sha1); - } - - private static String printBase64Binary(byte[] arr) { - return Base64.getEncoder().encodeToString(arr); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java deleted file mode 100644 index df26c6b41..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.security; - -/** - * Marker interface for an advanced keyring based on cryptography. - */ -public interface CryptoKeyring extends Keyring { - public void changePassword(char[] oldPassword, char[] newPassword); - - public void unlock(char[] password); -} diff --git a/org.argeo.cms/src/org/argeo/cms/security/Keyring.java b/org.argeo.cms/src/org/argeo/cms/security/Keyring.java deleted file mode 100644 index 53740c693..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/Keyring.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.security; - -import java.io.InputStream; - -/** - * Access to private (typically encrypted) data. The keyring is responsible for - * retrieving the necessary credentials. Experimental. This API may - * change. - */ -public interface Keyring { - /** - * Returns the confidential information as chars. Must ask for it if it is - * not stored. - */ - public char[] getAsChars(String path); - - /** - * Returns the confidential information as a stream. Must ask for it if it - * is not stored. - */ - public InputStream getAsStream(String path); - - public void set(String path, char[] arr); - - public void set(String path, InputStream in); -} diff --git a/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java b/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java deleted file mode 100644 index fb5394058..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.cms.security; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.api.cms.CmsConstants; - -public class NodeSecurityUtils { - public final static LdapName ROLE_ADMIN_NAME, ROLE_DATA_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, - ROLE_USER_ADMIN_NAME; - public final static List RESERVED_ROLES; - static { - try { - ROLE_ADMIN_NAME = new LdapName(CmsConstants.ROLE_ADMIN); - ROLE_DATA_ADMIN_NAME = new LdapName(CmsConstants.ROLE_DATA_ADMIN); - ROLE_USER_NAME = new LdapName(CmsConstants.ROLE_USER); - ROLE_USER_ADMIN_NAME = new LdapName(CmsConstants.ROLE_USER_ADMIN); - ROLE_ANONYMOUS_NAME = new LdapName(CmsConstants.ROLE_ANONYMOUS); - RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList( - new LdapName[] { ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, ROLE_USER_ADMIN_NAME })); - } catch (InvalidNameException e) { - throw new Error("Cannot initialize login module class", e); - } - } - - public static void checkUserName(LdapName name) throws IllegalArgumentException { - if (RESERVED_ROLES.contains(name)) - throw new IllegalArgumentException(name + " is a reserved name"); - } - - public static void checkImpliedPrincipalName(LdapName roleName) throws IllegalArgumentException { -// if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName)) -// throw new IllegalArgumentException(roleName + " cannot be listed as role"); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java b/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java deleted file mode 100644 index 13e8d753b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.argeo.cms.security; - -import javax.crypto.spec.PBEKeySpec; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.PasswordCallback; - -/** - * All information required to set up a {@link PBEKeySpec} bar the password - * itself (use a {@link PasswordCallback}) - */ -public class PBEKeySpecCallback implements Callback { - private String secretKeyFactory; - private byte[] salt; - private Integer iterationCount; - /** Can be null for some algorithms */ - private Integer keyLength; - /** Can be null, will trigger secret key encryption if not */ - private String secretKeyEncryption; - - private String encryptedPasswordHashCipher; - private byte[] encryptedPasswordHash; - - public void set(String secretKeyFactory, byte[] salt, - Integer iterationCount, Integer keyLength, - String secretKeyEncryption) { - this.secretKeyFactory = secretKeyFactory; - this.salt = salt; - this.iterationCount = iterationCount; - this.keyLength = keyLength; - this.secretKeyEncryption = secretKeyEncryption; -// this.encryptedPasswordHashCipher = encryptedPasswordHashCipher; -// this.encryptedPasswordHash = encryptedPasswordHash; - } - - public String getSecretKeyFactory() { - return secretKeyFactory; - } - - public byte[] getSalt() { - return salt; - } - - public Integer getIterationCount() { - return iterationCount; - } - - public Integer getKeyLength() { - return keyLength; - } - - public String getSecretKeyEncryption() { - return secretKeyEncryption; - } - - public String getEncryptedPasswordHashCipher() { - return encryptedPasswordHashCipher; - } - - public byte[] getEncryptedPasswordHash() { - return encryptedPasswordHash; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/security/package-info.java b/org.argeo.cms/src/org/argeo/cms/security/package-info.java deleted file mode 100644 index e99405436..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS reusable security components. */ -package org.argeo.cms.security; \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java b/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java deleted file mode 100644 index cfd482729..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.argeo.cms.tabular; - -import java.util.List; - -/** Minimal tabular row wrapping an {@link Object} array */ -public class ArrayTabularRow implements TabularRow { - private final Object[] arr; - - public ArrayTabularRow(List objs) { - this.arr = objs.toArray(); - } - - public Object get(Integer col) { - return arr[col]; - } - - public int size() { - return arr.length; - } - - public Object[] toArray() { - return arr; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java deleted file mode 100644 index 7f7ac1e61..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.cms.tabular; - -/** The column in a tabular content */ -public class TabularColumn { - private String name; - /** - * JCR types, see - * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html - * ?javax/jcr/PropertyType.html - */ - private Integer type; - - /** column with default type */ - public TabularColumn(String name) { - super(); - this.name = name; - } - - public TabularColumn(String name, Integer type) { - super(); - this.name = name; - this.type = type; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getType() { - return type; - } - - public void setType(Integer type) { - this.type = type; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java deleted file mode 100644 index c6d2ab88d..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.cms.tabular; - -import java.util.List; - -/** - * Content organized as a table, possibly with headers. Only JCR types are - * supported even though there is not direct dependency on JCR. - */ -public interface TabularContent { - /** The headers of this table or null is none available. */ - public List getColumns(); - - public TabularRowIterator read(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java deleted file mode 100644 index 69b973252..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.tabular; - -/** A row of tabular data */ -public interface TabularRow { - /** The value at this column index */ - public Object get(Integer col); - - /** The raw objects (direct references) */ - public Object[] toArray(); - - /** Number of columns */ - public int size(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java deleted file mode 100644 index 7ad8719e5..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.argeo.cms.tabular; - -import java.util.Iterator; - -/** Navigation of rows */ -public interface TabularRowIterator extends Iterator { - /** - * Current row number, has to be incremented by each call to next() ; starts at 0, will - * therefore be 1 for the first row returned. - */ - public Long getCurrentRowNumber(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java deleted file mode 100644 index 34fc85b7f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.cms.tabular; - - -/** Write to a tabular content */ -public interface TabularWriter { - /** Append a new row of data */ - public void appendRow(Object[] row); - - /** Finish persisting data and release resources */ - public void close(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/package-info.java b/org.argeo.cms/src/org/argeo/cms/tabular/package-info.java deleted file mode 100644 index 6cb48d07f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/tabular/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Tabular format API. */ -package org.argeo.cms.tabular; \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/util/CompositeString.java b/org.argeo.cms/src/org/argeo/cms/util/CompositeString.java new file mode 100644 index 000000000..8ea16f7f3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/CompositeString.java @@ -0,0 +1,164 @@ +package org.argeo.cms.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.StringTokenizer; + +/** A name that can be expressed with various conventions. */ +public class CompositeString { + public final static Character UNDERSCORE = Character.valueOf('_'); + public final static Character SPACE = Character.valueOf(' '); + public final static Character DASH = Character.valueOf('-'); + + private final String[] parts; + + // optimisation + private final int hashCode; + + public CompositeString(String str) { + Objects.requireNonNull(str, "String cannot be null"); + if ("".equals(str.trim())) + throw new IllegalArgumentException("String cannot be empty"); + if (!str.equals(str.trim())) + throw new IllegalArgumentException("String must be trimmed"); + this.parts = toParts(str); + hashCode = hashCode(this.parts); + } + + public String toString(char separator, boolean upperCase) { + StringBuilder sb = null; + for (String part : parts) { + if (sb == null) { + sb = new StringBuilder(); + } else { + sb.append(separator); + } + sb.append(upperCase ? part.toUpperCase() : part); + } + return sb.toString(); + } + + public String toStringCaml(boolean firstCharUpperCase) { + StringBuilder sb = null; + for (String part : parts) { + if (sb == null) {// first + sb = new StringBuilder(); + sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0)); + } else { + sb.append(Character.toUpperCase(part.charAt(0))); + } + + if (part.length() > 1) + sb.append(part.substring(1)); + } + return sb.toString(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof CompositeString)) + return false; + + CompositeString other = (CompositeString) obj; + return Arrays.equals(parts, other.parts); + } + + @Override + public String toString() { + return toString(DASH, false); + } + + public static String[] toParts(String str) { + Character separator = null; + if (str.indexOf(UNDERSCORE) >= 0) { + checkNo(str, SPACE); + checkNo(str, DASH); + separator = UNDERSCORE; + } else if (str.indexOf(DASH) >= 0) { + checkNo(str, SPACE); + checkNo(str, UNDERSCORE); + separator = DASH; + } else if (str.indexOf(SPACE) >= 0) { + checkNo(str, DASH); + checkNo(str, UNDERSCORE); + separator = SPACE; + } + + List res = new ArrayList<>(); + if (separator != null) { + StringTokenizer st = new StringTokenizer(str, separator.toString()); + while (st.hasMoreTokens()) { + res.add(st.nextToken().toLowerCase()); + } + } else { + // single + String strLowerCase = str.toLowerCase(); + if (str.toUpperCase().equals(str) || strLowerCase.equals(str)) + return new String[] { strLowerCase }; + + // CAML + StringBuilder current = null; + for (char c : str.toCharArray()) { + if (Character.isUpperCase(c)) { + if (current != null) + res.add(current.toString()); + current = new StringBuilder(); + } + if (current == null)// first char is lower case + current = new StringBuilder(); + current.append(Character.toLowerCase(c)); + } + res.add(current.toString()); + } + return res.toArray(new String[res.size()]); + } + + private static void checkNo(String str, Character c) { + if (str.indexOf(c) >= 0) { + throw new IllegalArgumentException("Only one kind of sperator is allowed"); + } + } + + private static int hashCode(String[] parts) { + int hashCode = 0; + for (String part : parts) { + hashCode = hashCode + part.hashCode(); + } + return hashCode; + } + + static boolean smokeTests() { + CompositeString plainName = new CompositeString("NAME"); + assert "name".equals(plainName.toString()); + assert "NAME".equals(plainName.toString(UNDERSCORE, true)); + assert "name".equals(plainName.toString(UNDERSCORE, false)); + assert "name".equals(plainName.toStringCaml(false)); + assert "Name".equals(plainName.toStringCaml(true)); + + CompositeString camlName = new CompositeString("myComplexName"); + + assert new CompositeString("my-complex-name").equals(camlName); + assert new CompositeString("MY_COMPLEX_NAME").equals(camlName); + assert new CompositeString("My complex Name").equals(camlName); + assert new CompositeString("MyComplexName").equals(camlName); + + assert "my-complex-name".equals(camlName.toString()); + assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true)); + assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false)); + assert "myComplexName".equals(camlName.toStringCaml(false)); + assert "MyComplexName".equals(camlName.toStringCaml(true)); + + return CompositeString.class.desiredAssertionStatus(); + } + + public static void main(String[] args) { + System.out.println(smokeTests()); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/CsvParser.java b/org.argeo.cms/src/org/argeo/cms/util/CsvParser.java new file mode 100644 index 000000000..f22a1e45f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/CsvParser.java @@ -0,0 +1,242 @@ +package org.argeo.cms.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Parses a CSV file interpreting the first line as a header. The + * {@link #parse(InputStream)} method and the setters are synchronized so that + * the object cannot be modified when parsing. + */ +public abstract class CsvParser { + private char separator = ','; + private char quote = '\"'; + + private Boolean noHeader = false; + private Boolean strictLineAsLongAsHeader = true; + + /** + * Actually process a parsed line. If + * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header + * and the tokens are guaranteed to have the same size. + * + * @param lineNumber the current line number, starts at 1 (the header, if header + * processing is enabled, the first line otherwise) + * @param header the read-only header or null if + * {@link #setNoHeader(Boolean)} is true (default is false) + * @param tokens the parsed tokens + */ + protected abstract void processLine(Integer lineNumber, List header, List 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 header = null; + if (!noHeader) { + String headerStr = bufferedReader.readLine(); + if (headerStr == null)// empty file + return; + lineCount++; + header = new ArrayList(); + 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 tokens = new ArrayList(); + 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 tokens, StringBuffer currStr, Boolean wasInquote) { + if (wasInquote) + currStr.append('\n'); + + char[] arr = str.toCharArray(); + boolean inQuote = wasInquote; + for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + if (c == separator) { + if (!inQuote) { + tokens.add(currStr.toString()); +// currStr.delete(0, currStr.length()); + currStr.setLength(0); + currStr.trimToSize(); + } else { + // we don't remove separator that are in a quoted substring + // System.out + // .println("IN QUOTE, got a separator: [" + c + "]"); + currStr.append(c); + } + } else if (c == quote) { + if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) { + // case of double quote + currStr.append(quote); + i++; + } else {// standard + inQuote = inQuote ? false : true; + } + } else { + currStr.append(c); + } + } + + if (!inQuote) { + tokens.add(currStr.toString()); + // System.out.println("# TOKEN: " + currStr); + } + // if (inQuote) + // throw new ArgeoException("Missing quote at the end of the line " + // + str + " (parsed: " + tokens + ")"); + if (inQuote) + return true; + else + return false; + // return tokens; + } + + public char getSeparator() { + return separator; + } + + public synchronized void setSeparator(char separator) { + this.separator = separator; + } + + public char getQuote() { + return quote; + } + + public synchronized void setQuote(char quote) { + this.quote = quote; + } + + public Boolean getNoHeader() { + return noHeader; + } + + public synchronized void setNoHeader(Boolean noHeader) { + this.noHeader = noHeader; + } + + public Boolean getStrictLineAsLongAsHeader() { + return strictLineAsLongAsHeader; + } + + public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) { + this.strictLineAsLongAsHeader = strictLineAsLongAsHeader; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/CsvParserWithLinesAsMap.java b/org.argeo.cms/src/org/argeo/cms/util/CsvParserWithLinesAsMap.java new file mode 100644 index 000000000..0a0382c1d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/CsvParserWithLinesAsMap.java @@ -0,0 +1,36 @@ +package org.argeo.cms.util; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * CSV parser allowing to process lines as maps whose keys are the header + * fields. + */ +public abstract class CsvParserWithLinesAsMap extends CsvParser { + + /** + * Actually processes a line. + * + * @param lineNumber the current line number, starts at 1 (the header, if header + * processing is enabled, the first lien otherwise) + * @param line the parsed tokens as a map whose keys are the header fields + */ + protected abstract void processLine(Integer lineNumber, Map line); + + protected final void processLine(Integer lineNumber, List header, List tokens) { + if (header == null) + throw new IllegalArgumentException("Only CSV with header is supported"); + Map line = new HashMap(); + for (int i = 0; i < header.size(); i++) { + String key = header.get(i); + String value = null; + if (i < tokens.size()) + value = tokens.get(i); + line.put(key, value); + } + processLine(lineNumber, line); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/CsvWriter.java b/org.argeo.cms/src/org/argeo/cms/util/CsvWriter.java new file mode 100644 index 000000000..902e6bbee --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/CsvWriter.java @@ -0,0 +1,156 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; + +/** Write in CSV format. */ +public class CsvWriter { + private final Writer out; + + private char separator = ','; + private char quote = '\"'; + + /** + * Creates a CSV writer. + * + * @param out the stream to write to. Caller is responsible for closing it. + * + * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead. + * + */ + @Deprecated + public CsvWriter(OutputStream out) { + this.out = new OutputStreamWriter(out); + } + + /** + * Creates a CSV writer. + * + * @param out the stream to write to. Caller is responsible for closing it. + * @param encoding the encoding to use. + * + * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead. + */ + @Deprecated + public CsvWriter(OutputStream out, String encoding) { + try { + this.out = new OutputStreamWriter(out, encoding); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * Creates a CSV writer. + * + * @param out the stream to write to. Caller is responsible for closing it. + * @param charset the charset to use + */ + public CsvWriter(OutputStream out, Charset charset) { + this.out = new OutputStreamWriter(out, charset); + } + + /** + * Creates a CSV writer. + * + * @param out the stream to write to. Caller is responsible for closing it. + */ + public CsvWriter(Writer writer) { + this.out = writer; + } + + /** + * Write a CSV line. Also used to write a header if needed (this is transparent + * for the CSV writer): simply call it first, before writing the lines. + */ + public void writeLine(List tokens) { + try { + Iterator it = tokens.iterator(); + while (it.hasNext()) { + Object obj = it.next(); + writeToken(obj != null ? obj.toString() : null); + if (it.hasNext()) + out.write(separator); + } + out.write('\n'); + out.flush(); + } catch (IOException e) { + throw new RuntimeException("Could not write " + tokens, e); + } + } + + /** + * Write a CSV line. Also used to write a header if needed (this is transparent + * for the CSV writer): simply call it first, before writing the lines. + */ + public void writeLine(Object... tokens) { + try { + for (int i = 0; i < tokens.length; i++) { + if (tokens[i] == null) { + writeToken(null); + } else { + writeToken(tokens[i].toString()); + } + if (i != (tokens.length - 1)) + out.write(separator); + } + out.write('\n'); + out.flush(); + } catch (IOException e) { + throw new RuntimeException("Could not write " + tokens, e); + } + } + + protected void writeToken(String token) throws IOException { + if (token == null) { + // TODO configure how to deal with null + out.write(""); + return; + } + // +2 for possible quotes, another +2 assuming there would be an already + // quoted string where quotes needs to be duplicated + // another +2 for safety + // we don't want to increase buffer size while writing + StringBuffer buf = new StringBuffer(token.length() + 6); + char[] arr = token.toCharArray(); + boolean shouldQuote = false; + for (char c : arr) { + if (!shouldQuote) { + if (c == separator) + shouldQuote = true; + if (c == '\n') + shouldQuote = true; + } + + if (c == quote) { + shouldQuote = true; + // duplicate quote + buf.append(quote); + } + + // generic case + buf.append(c); + } + + if (shouldQuote == true) + out.write(quote); + out.write(buf.toString()); + if (shouldQuote == true) + out.write(quote); + } + + public void setSeparator(char separator) { + this.separator = separator; + } + + public void setQuote(char quote) { + this.quote = quote; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/CurrentSubject.java b/org.argeo.cms/src/org/argeo/cms/util/CurrentSubject.java new file mode 100644 index 000000000..6a3dcbc62 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/CurrentSubject.java @@ -0,0 +1,65 @@ +package org.argeo.cms.util; + +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionException; + +import javax.security.auth.Subject; + +/** + * Prepare evolution of Java APIs introduced in JDK 18, as these static methods + * will be added to {@link Subject}. + */ +@SuppressWarnings("removal") +public class CurrentSubject { + + private final static boolean useThreadLocal = Boolean + .parseBoolean(System.getProperty("jdk.security.auth.subject.useTL")); + + private final static InheritableThreadLocal current = new InheritableThreadLocal<>(); + + public static Subject current() { + if (useThreadLocal) { + return current.get(); + } else {// legacy + Subject subject = Subject.getSubject(AccessController.getContext()); + return subject; + } + } + + public static T callAs(Subject subject, Callable action) { + if (useThreadLocal) { + Subject previous = current(); + current.set(subject); + try { + return action.call(); + } catch (Exception e) { + throw new CompletionException("Failed to execute action for " + subject, e); + } finally { + current.set(previous); + } + } else {// legacy + try { + return Subject.doAs(subject, new PrivilegedExceptionAction() { + + @Override + public T run() throws Exception { + return action.call(); + } + + }); + } catch (PrivilegedActionException e) { + throw new CompletionException("Failed to execute action for " + subject, e.getCause()); + } catch (Exception e) { + throw new CompletionException("Failed to execute action for " + subject, e); + } + } + } + + /** Singleton. */ + private CurrentSubject() { + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/DictionaryKeys.java b/org.argeo.cms/src/org/argeo/cms/util/DictionaryKeys.java new file mode 100644 index 000000000..a9f6a318f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/DictionaryKeys.java @@ -0,0 +1,42 @@ +package org.argeo.cms.util; + +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout + * the OSGi APIs) as an {@link Iterable} so that they are easily usable in + * for-each loops. + */ +class DictionaryKeys implements Iterable { + private final Dictionary dictionary; + + public DictionaryKeys(Dictionary dictionary) { + this.dictionary = dictionary; + } + + @Override + public Iterator iterator() { + return new KeyIterator(dictionary.keys()); + } + + private static class KeyIterator implements Iterator { + private final Enumeration keys; + + KeyIterator(Enumeration keys) { + this.keys = keys; + } + + @Override + public boolean hasNext() { + return keys.hasMoreElements(); + } + + @Override + public String next() { + return keys.nextElement(); + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/DigestUtils.java b/org.argeo.cms/src/org/argeo/cms/util/DigestUtils.java new file mode 100644 index 000000000..047749f70 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/DigestUtils.java @@ -0,0 +1,202 @@ +package org.argeo.cms.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** Utilities around cryptographic digests */ +public class DigestUtils { + public final static String MD5 = "MD5"; + public final static String SHA1 = "SHA1"; + public final static String SHA256 = "SHA-256"; + public final static String SHA512 = "SHA-512"; + + private static Boolean debug = false; + // TODO: make it configurable + private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB + + public static byte[] sha1(byte[]... bytes) { + try { + MessageDigest digest = MessageDigest.getInstance(SHA1); + for (byte[] arr : bytes) + digest.update(arr); + byte[] checksum = digest.digest(); + return checksum; + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("SHA1 is not avalaible", e); + } + } + + public static byte[] digestAsBytes(String algorithm, byte[]... bytes) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + for (byte[] arr : bytes) + digest.update(arr); + byte[] checksum = digest.digest(); + return checksum; + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e); + } + } + + public static String digest(String algorithm, byte[]... bytes) { + return toHexString(digestAsBytes(algorithm, bytes)); + } + + public static String digest(String algorithm, InputStream in) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + // ReadableByteChannel channel = Channels.newChannel(in); + // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity); + // while (channel.read(bb) > 0) + // digest.update(bb); + byte[] buffer = new byte[byteBufferCapacity]; + int read = 0; + while ((read = in.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + + byte[] checksum = digest.digest(); + String res = toHexString(checksum); + return res; + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + StreamUtils.closeQuietly(in); + } + } + + public static String digest(String algorithm, File file) { + FileInputStream fis = null; + FileChannel fc = null; + try { + fis = new FileInputStream(file); + fc = fis.getChannel(); + + // Get the file's size and then map it into memory + int sz = (int) fc.size(); + ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz); + return digest(algorithm, bb); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e); + } finally { + StreamUtils.closeQuietly(fis); + if (fc.isOpen()) + try { + fc.close(); + } catch (IOException e) { + // silent + } + } + } + + protected static String digest(String algorithm, ByteBuffer bb) { + long begin = System.currentTimeMillis(); + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + digest.update(bb); + byte[] checksum = digest.digest(); + String res = toHexString(checksum); + long end = System.currentTimeMillis(); + if (debug) + System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s"); + return res; + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); + } + } + + public static String sha1hex(Path path) { + return digest(SHA1, path, byteBufferCapacity); + } + + public static String digest(String algorithm, Path path, long bufferSize) { + byte[] digest = digestAsBytes(algorithm, path, bufferSize); + return toHexString(digest); + } + + public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) { + long begin = System.currentTimeMillis(); + try { + MessageDigest md = MessageDigest.getInstance(algorithm); + FileChannel fc = FileChannel.open(file); + long fileSize = Files.size(file); + if (fileSize <= bufferSize) { + ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize); + md.update(bb); + } else { + long lastCycle = (fileSize / bufferSize) - 1; + long position = 0; + for (int i = 0; i <= lastCycle; i++) { + ByteBuffer bb; + if (i != lastCycle) { + bb = fc.map(MapMode.READ_ONLY, position, bufferSize); + position = position + bufferSize; + } else { + bb = fc.map(MapMode.READ_ONLY, position, fileSize - position); + position = fileSize; + } + md.update(bb); + } + } + long end = System.currentTimeMillis(); + if (debug) + System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s"); + return md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e); + } catch (IOException e) { + throw new RuntimeException("Cannot digest " + file + " with algorithm " + algorithm, e); + } + } + + public static void main(String[] args) { + File file; + if (args.length > 0) + file = new File(args[0]); + else { + System.err.println("Usage: []" + " (see http://java.sun.com/j2se/1.5.0/" + + "docs/guide/security/CryptoSpec.html#AppA)"); + return; + } + + if (args.length > 1) { + String algorithm = args[1]; + System.out.println(digest(algorithm, file)); + } else { + String algorithm = "MD5"; + System.out.println(algorithm + ": " + digest(algorithm, file)); + algorithm = "SHA"; + System.out.println(algorithm + ": " + digest(algorithm, file)); + System.out.println(algorithm + ": " + sha1hex(file.toPath())); + algorithm = "SHA-256"; + System.out.println(algorithm + ": " + digest(algorithm, file)); + algorithm = "SHA-512"; + System.out.println(algorithm + ": " + digest(algorithm, file)); + } + } + + final private static char[] hexArray = "0123456789abcdef".toCharArray(); + + /** Converts a byte array to an hex String. */ + public static String toHexString(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/DirH.java b/org.argeo.cms/src/org/argeo/cms/util/DirH.java new file mode 100644 index 000000000..2596c61d1 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/DirH.java @@ -0,0 +1,116 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Hashes the hashes of the files in a directory. */ +public class DirH { + + private final static Charset charset = Charset.forName("UTF-16"); + private final static long bufferSize = 200 * 1024 * 1024; + private final static String algorithm = "SHA"; + + private final static byte EOL = (byte) '\n'; + private final static byte SPACE = (byte) ' '; + + private final int hashSize; + + private final byte[][] hashes; + private final byte[][] fileNames; + private final byte[] digest; + private final byte[] dirName; + + /** + * @param dirName can be null or empty + */ + private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) { + if (hashes.length != fileNames.length) + throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names"); + this.hashes = hashes; + this.fileNames = fileNames; + this.dirName = dirName == null ? new byte[0] : dirName; + if (hashes.length == 0) {// empty dir + hashSize = 20; + // FIXME what is the digest of an empty dir? + digest = new byte[hashSize]; + Arrays.fill(digest, SPACE); + return; + } + hashSize = hashes[0].length; + for (int i = 0; i < hashes.length; i++) { + if (hashes[i].length != hashSize) + throw new IllegalArgumentException( + "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length); + } + + try { + MessageDigest md = MessageDigest.getInstance(algorithm); + for (int i = 0; i < hashes.length; i++) { + md.update(this.hashes[i]); + md.update(SPACE); + md.update(this.fileNames[i]); + md.update(EOL); + } + digest = md.digest(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Cannot digest", e); + } + } + + public void print(PrintStream out) { + out.print(DigestUtils.toHexString(digest)); + if (dirName.length > 0) { + out.print(' '); + out.print(new String(dirName, charset)); + } + out.print('\n'); + for (int i = 0; i < hashes.length; i++) { + out.print(DigestUtils.toHexString(hashes[i])); + out.print(' '); + out.print(new String(fileNames[i], charset)); + out.print('\n'); + } + } + + public static DirH digest(Path dir) { + try (DirectoryStream files = Files.newDirectoryStream(dir)) { + List hs = new ArrayList(); + List fNames = new ArrayList<>(); + for (Path file : files) { + if (!Files.isDirectory(file)) { + byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize); + hs.add(digest); + fNames.add(file.getFileName().toString()); + } + } + + byte[][] fileNames = new byte[fNames.size()][]; + for (int i = 0; i < fNames.size(); i++) { + fileNames[i] = fNames.get(i).getBytes(charset); + } + byte[][] hashes = hs.toArray(new byte[hs.size()][]); + return new DirH(hashes, fileNames, dir.toString().getBytes(charset)); + } catch (IOException e) { + throw new RuntimeException("Cannot digest " + dir, e); + } + } + + public static void main(String[] args) { + try { + DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/")); + dirH.print(System.out); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/ExceptionsChain.java b/org.argeo.cms/src/org/argeo/cms/util/ExceptionsChain.java new file mode 100644 index 000000000..e71cfb3ca --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/ExceptionsChain.java @@ -0,0 +1,90 @@ +package org.argeo.cms.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Serialisable wrapper of a {@link Throwable}. typically to be written as XML + * or JSON in a server error response. + */ +public class ExceptionsChain { + private List exceptions = new ArrayList<>(); + + public ExceptionsChain() { + } + + public ExceptionsChain(Throwable exception) { + writeException(exception); + } + + /** recursive */ + protected void writeException(Throwable exception) { + SystemException systemException = new SystemException(exception); + exceptions.add(systemException); + Throwable cause = exception.getCause(); + if (cause != null) + writeException(cause); + } + + public List getExceptions() { + return exceptions; + } + + public void setExceptions(List exceptions) { + this.exceptions = exceptions; + } + + /** An exception in the chain. */ + public static class SystemException { + private String type; + private String message; + private List stackTrace; + + public SystemException() { + } + + public SystemException(Throwable exception) { + this.type = exception.getClass().getName(); + this.message = exception.getMessage(); + this.stackTrace = new ArrayList<>(); + StackTraceElement[] elems = exception.getStackTrace(); + for (int i = 0; i < elems.length; i++) + stackTrace.add("at " + elems[i].toString()); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getStackTrace() { + return stackTrace; + } + + public void setStackTrace(List stackTrace) { + this.stackTrace = stackTrace; + } + + @Override + public String toString() { + return "System exception: " + type + ", " + message + ", " + stackTrace; + } + + } + + @Override + public String toString() { + return exceptions.toString(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/FsUtils.java b/org.argeo.cms/src/org/argeo/cms/util/FsUtils.java new file mode 100644 index 000000000..26c05b60e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/FsUtils.java @@ -0,0 +1,78 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** Utilities around the standard Java file abstractions. */ +public class FsUtils { + + /** Deletes this path, recursively if needed. */ + public static void copyDirectory(Path source, Path target) { + if (!Files.exists(source) || !Files.isDirectory(source)) + throw new IllegalArgumentException(source + " is not a directory"); + if (Files.exists(target) && !Files.isDirectory(target)) + throw new IllegalArgumentException(target + " is not a directory"); + try { + Files.createDirectories(target); + Files.walkFileTree(source, new SimpleFileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException { + Path relativePath = source.relativize(directory); + Path targetDirectory = target.resolve(relativePath); + if (!Files.exists(targetDirectory)) + Files.createDirectory(targetDirectory); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path relativePath = source.relativize(file); + Path targetFile = target.resolve(relativePath); + Files.copy(file, targetFile); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new RuntimeException("Cannot copy " + source + " to " + target, e); + } + + } + + /** + * Deletes this path, recursively if needed. Does nothing if the path does not + * exist. + */ + public static void delete(Path path) { + try { + if (!Files.exists(path)) + return; + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException { + if (e != null) + throw e; + Files.delete(directory); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new RuntimeException("Cannot delete " + path, e); + } + } + + /** Singleton. */ + private FsUtils() { + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/LangUtils.java b/org.argeo.cms/src/org/argeo/cms/util/LangUtils.java new file mode 100644 index 000000000..0e214271d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/LangUtils.java @@ -0,0 +1,331 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +/** Utilities around Java basic features. */ +public class LangUtils { + /* + * NON-API OSGi + */ + /** + * Returns an array with the names of the provided classes. Useful when + * registering services with multiple interfaces in OSGi. + */ + public static String[] names(Class... clzz) { + String[] res = new String[clzz.length]; + for (int i = 0; i < clzz.length; i++) + res[i] = clzz[i].getName(); + return res; + } + +// /* +// * MAP +// */ +// /** +// * Creates a new {@link Map} with one key-value pair. Key should not be null, +// * but if the value is null, it returns an empty {@link Map}. +// * +// * @deprecated Use {@link Collections#singletonMap(Object, Object)} instead. +// */ +// @Deprecated +// public static Map map(String key, Object value) { +// assert key != null; +// HashMap 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 dict(String key, Object value) { + assert key != null; + Hashtable props = new Hashtable<>(); + if (value != null) + props.put(key, value); + return props; + } + + /** @deprecated Use {@link #dict(String, Object)} instead. */ + @Deprecated + public static Dictionary dico(String key, Object value) { + return dict(key, value); + } + + /** Converts a {@link Dictionary} to a {@link Map} of strings. */ + public static Map dictToStringMap(Dictionary properties) { + if (properties == null) { + return null; + } + Map res = new HashMap<>(properties.size()); + Enumeration keys = properties.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + res.put(key, properties.get(key).toString()); + } + return res; + } + + /** Converts a {@link Dictionary} to a {@link Map}. */ + public static Map dictToMap(Dictionary properties) { + if (properties == null) { + return null; + } + Map res = new HashMap<>(properties.size()); + Enumeration keys = properties.keys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + res.put(key, properties.get(key)); + } + return res; + } + + /** + * Get a string property from this map, expecting to find it, or + * null if not found. + */ + public static String get(Map 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 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 keys(Dictionary props) { + assert props != null; + return new DictionaryKeys(props); + } + + static String toJson(Dictionary props) { + return toJson(props, false); + } + + static String toJson(Dictionary props, boolean pretty) { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + if (pretty) + sb.append('\n'); + Enumeration 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 props, Path path) throws IOException { + if (props == null) + throw new IllegalArgumentException("Props cannot be null"); + Properties toStore = new Properties(); + for (Enumeration 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 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 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 loadFromProperties(Path path) throws IOException { + Properties toLoad = new Properties(); + try (InputStream in = Files.newInputStream(path)) { + toLoad.load(in); + } + Dictionary res = new Hashtable(); + 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 null. + */ + public static List toStringList(Object value) { + List values = new ArrayList<>(); + if (value == null) + return values; + String[] arr; + if (value instanceof String) { + arr = ((String) value).split(","); + } else if (value instanceof String[]) { + arr = (String[]) value; + } else { + throw new IllegalArgumentException("Unsupported value type " + value.getClass()); + } + for (String str : arr) { + values.add(str.trim()); + } + return values; + } + + /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */ + public static int size(Iterable iterable) { + if (iterable instanceof Collection) + return ((Collection) iterable).size(); + + int size = 0; + for (Iterator it = iterable.iterator(); it.hasNext(); size++) + it.next(); + return size; + } + + public static T getAt(Iterable iterable, int index) { + if (iterable instanceof List) { + List lst = ((List) iterable); + if (index >= lst.size()) + throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")"); + return lst.get(index); + } + int i = 0; + for (Iterator it = iterable.iterator(); it.hasNext(); i++) { + if (i == index) + return it.next(); + else + it.next(); + } + throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")"); + } + + /* + * EXCEPTIONS + */ + /** + * Chain the messages of all causes (one per line, starts with a line + * return) without all the stack + */ + public static String chainCausesMessages(Throwable t) { + StringBuffer buf = new StringBuffer(); + chainCauseMessage(buf, t); + return buf.toString(); + } + + /** Recursive chaining of messages */ + private static void chainCauseMessage(StringBuffer buf, Throwable t) { + buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage()); + if (t.getCause() != null) + chainCauseMessage(buf, t.getCause()); + } + + /* + * TIME + */ + /** Formats time elapsed since start. */ + public static String since(ZonedDateTime start) { + ZonedDateTime now = ZonedDateTime.now(); + return duration(start, now); + } + + /** Formats a duration. */ + public static String duration(Temporal start, Temporal end) { + long count = ChronoUnit.DAYS.between(start, end); + if (count != 0) + return count > 1 ? count + " days" : count + " day"; + count = ChronoUnit.HOURS.between(start, end); + if (count != 0) + return count > 1 ? count + " hours" : count + " hours"; + count = ChronoUnit.MINUTES.between(start, end); + if (count != 0) + return count > 1 ? count + " minutes" : count + " minute"; + count = ChronoUnit.SECONDS.between(start, end); + return count > 1 ? count + " seconds" : count + " second"; + } + + /** Singleton constructor. */ + private LangUtils() { + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/OS.java b/org.argeo.cms/src/org/argeo/cms/util/OS.java new file mode 100644 index 000000000..c63d7a190 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/OS.java @@ -0,0 +1,65 @@ +package org.argeo.cms.util; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** When OS specific informations are needed. */ +public class OS { + public final static OS LOCAL = new OS(); + + private final String arch, name, version; + + /** The OS of the running JVM */ + protected OS() { + arch = System.getProperty("os.arch"); + name = System.getProperty("os.name"); + version = System.getProperty("os.version"); + } + + public String getArch() { + return arch; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public boolean isMSWindows() { + // only MS Windows would use such an horrendous separator... + return File.separatorChar == '\\'; + } + + public String[] getDefaultShellCommand() { + if (!isMSWindows()) + return new String[] { "/bin/bash", "-l", "-i" }; + else + return new String[] { "cmd.exe", "/C" }; + } + + public static long getJvmPid() { + return ProcessHandle.current().pid(); +// String pidAndHost = ManagementFactory.getRuntimeMXBean().getName(); +// return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@'))); + } + + /** + * Get the runtime directory. It will be the environment variable + * XDG_RUNTIME_DIR if it is set, or ~/.cache/argeo if not. + */ + public static Path getRunDir() { + Path runDir; + String xdgRunDir = System.getenv("XDG_RUNTIME_DIR"); + if (xdgRunDir != null) { + // TODO support multiple names + runDir = Paths.get(xdgRunDir); + } else { + runDir = Paths.get(System.getProperty("user.home"), ".cache/argeo"); + } + return runDir; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/PasswordEncryption.java b/org.argeo.cms/src/org/argeo/cms/util/PasswordEncryption.java new file mode 100644 index 000000000..c50f415e3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/PasswordEncryption.java @@ -0,0 +1,216 @@ +package org.argeo.cms.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +public class PasswordEncryption { + public final static Integer DEFAULT_ITERATION_COUNT = 1024; + /** Stronger with 256, but causes problem with Oracle JVM */ + public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256; + public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128; + public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; + public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; + public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; +// public final static String DEFAULT_CHARSET = "UTF-8"; + public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private Integer iterationCount = DEFAULT_ITERATION_COUNT; + private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH; + private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; + private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; + private String cipherName = DEFAULT_CIPHER_NAME; + + private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, + (byte) 0x35, (byte) 0xE3, (byte) 0x03 }; + private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, + (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, + (byte) 0x35, (byte) 0xE3, (byte) 0x03 }; + + private Key key; + private Cipher ecipher; + private Cipher dcipher; + + private String securityProviderName = null; + + /** + * This is up to the caller to clear the passed array. Neither copy of nor + * reference to the passed array is kept + */ + public PasswordEncryption(char[] password) { + this(password, DEFAULT_SALT_8, DEFAULT_IV_16); + } + + /** + * This is up to the caller to clear the passed array. Neither copies of nor + * references to the passed arrays are kept + */ + public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) { + try { + initKeyAndCiphers(password, passwordSalt, initializationVector); + } catch (InvalidKeyException e) { + Integer previousSecreteKeyLength = secreteKeyLength; + secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED; + System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength + + " secrete key length instead of " + previousSecreteKeyLength); + try { + initKeyAndCiphers(password, passwordSalt, initializationVector); + } catch (GeneralSecurityException e1) { + throw new IllegalStateException("Cannot get secret key (with restricted length)", e1); + } + } catch (GeneralSecurityException e) { + throw new IllegalStateException("Cannot get secret key", e); + } + } + + protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector) + throws GeneralSecurityException { + byte[] salt = new byte[8]; + System.arraycopy(passwordSalt, 0, salt, 0, salt.length); + // for (int i = 0; i < password.length && i < salt.length; i++) + // salt[i] = (byte) password[i]; + byte[] iv = new byte[16]; + System.arraycopy(initializationVector, 0, iv, 0, iv.length); + + SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName()); + PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength()); + String secKeyEncryption = getSecretKeyEncryption(); + if (secKeyEncryption != null) { + SecretKey tmp = keyFac.generateSecret(keySpec); + key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption()); + } else { + key = keyFac.generateSecret(keySpec); + } + if (securityProviderName != null) + ecipher = Cipher.getInstance(getCipherName(), securityProviderName); + else + ecipher = Cipher.getInstance(getCipherName()); + ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); + dcipher = Cipher.getInstance(getCipherName()); + dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + } + + public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException { + try { + CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher); + StreamUtils.copy(decryptedIn, out); + StreamUtils.closeQuietly(out); + } catch (IOException e) { + throw e; + } finally { + StreamUtils.closeQuietly(decryptedIn); + } + } + + public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException { + try { + CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher); + StreamUtils.copy(decryptedIn, decryptedOut); + } catch (IOException e) { + throw e; + } finally { + StreamUtils.closeQuietly(encryptedIn); + } + } + + public byte[] encryptString(String str) { + ByteArrayOutputStream out = null; + ByteArrayInputStream in = null; + try { + out = new ByteArrayOutputStream(); + in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET)); + encrypt(in, out); + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + StreamUtils.closeQuietly(out); + } + } + + /** Closes the input stream */ + public String decryptAsString(InputStream in) { + ByteArrayOutputStream out = null; + try { + out = new ByteArrayOutputStream(); + decrypt(in, out); + return new String(out.toByteArray(), DEFAULT_CHARSET); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + StreamUtils.closeQuietly(out); + } + } + + protected Key getKey() { + return key; + } + + protected Cipher getEcipher() { + return ecipher; + } + + protected Cipher getDcipher() { + return dcipher; + } + + protected Integer getIterationCount() { + return iterationCount; + } + + protected Integer getKeyLength() { + return secreteKeyLength; + } + + protected String getSecretKeyFactoryName() { + return secreteKeyFactoryName; + } + + protected String getSecretKeyEncryption() { + return secreteKeyEncryption; + } + + protected String getCipherName() { + return cipherName; + } + + public void setIterationCount(Integer iterationCount) { + this.iterationCount = iterationCount; + } + + public void setSecreteKeyLength(Integer keyLength) { + this.secreteKeyLength = keyLength; + } + + public void setSecreteKeyFactoryName(String secreteKeyFactoryName) { + this.secreteKeyFactoryName = secreteKeyFactoryName; + } + + public void setSecreteKeyEncryption(String secreteKeyEncryption) { + this.secreteKeyEncryption = secreteKeyEncryption; + } + + public void setCipherName(String cipherName) { + this.cipherName = cipherName; + } + + public void setSecurityProviderName(String securityProviderName) { + this.securityProviderName = securityProviderName; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/ServiceChannel.java b/org.argeo.cms/src/org/argeo/cms/util/ServiceChannel.java new file mode 100644 index 000000000..8cdbcadc4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/ServiceChannel.java @@ -0,0 +1,78 @@ +package org.argeo.cms.util; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousByteChannel; +import java.nio.channels.CompletionHandler; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */ +public class ServiceChannel implements AsynchronousByteChannel { + private final ReadableByteChannel in; + private final WritableByteChannel out; + + private boolean open = true; + + private ExecutorService executor; + + public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) { + this.in = in; + this.out = out; + this.executor = executor; + } + + @Override + public Future read(ByteBuffer dst) { + return executor.submit(() -> in.read(dst)); + } + + @Override + public void read(ByteBuffer dst, A attachment, CompletionHandler handler) { + try { + Future res = read(dst); + handler.completed(res.get(), attachment); + } catch (Exception e) { + handler.failed(e, attachment); + } + } + + @Override + public Future write(ByteBuffer src) { + return executor.submit(() -> out.write(src)); + } + + @Override + public void write(ByteBuffer src, A attachment, CompletionHandler handler) { + try { + Future res = write(src); + handler.completed(res.get(), attachment); + } catch (Exception e) { + handler.failed(e, attachment); + } + } + + @Override + public synchronized void close() throws IOException { + try { + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + out.close(); + } catch (Exception e) { + e.printStackTrace(); + } + open = false; + notifyAll(); + } + + @Override + public synchronized boolean isOpen() { + return open; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java b/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java new file mode 100644 index 000000000..a589e739a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java @@ -0,0 +1,98 @@ +package org.argeo.cms.util; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.StringJoiner; + +/** Stream utilities to be used when Apache Commons IO is not available. */ +public class StreamUtils { + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /* + * APACHE COMMONS IO (inspired) + */ + + /** @return the number of bytes */ + public static Long copy(InputStream in, OutputStream out) throws IOException { + Long count = 0l; + byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; + while (true) { + int length = in.read(buf); + if (length < 0) + break; + out.write(buf, 0, length); + count = count + length; + } + return count; + } + + /** @return the number of chars */ + public static Long copy(Reader in, Writer out) throws IOException { + Long count = 0l; + char[] buf = new char[DEFAULT_BUFFER_SIZE]; + while (true) { + int length = in.read(buf); + if (length < 0) + break; + out.write(buf, 0, length); + count = count + length; + } + return count; + } + + public static byte[] toByteArray(InputStream in) throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + copy(in, out); + return out.toByteArray(); + } + } + + public static void closeQuietly(InputStream in) { + if (in != null) + try { + in.close(); + } catch (Exception e) { + // + } + } + + public static void closeQuietly(OutputStream out) { + if (out != null) + try { + out.close(); + } catch (Exception e) { + // + } + } + + public static void closeQuietly(Reader in) { + if (in != null) + try { + in.close(); + } catch (Exception e) { + // + } + } + + public static void closeQuietly(Writer out) { + if (out != null) + try { + out.close(); + } catch (Exception e) { + // + } + } + + public static String toString(BufferedReader reader) throws IOException { + StringJoiner sn = new StringJoiner("\n"); + String line = null; + while ((line = reader.readLine()) != null) + sn.add(line); + return sn.toString(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/Tester.java b/org.argeo.cms/src/org/argeo/cms/util/Tester.java new file mode 100644 index 000000000..fa62cd796 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/Tester.java @@ -0,0 +1,126 @@ +package org.argeo.cms.util; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** A generic tester based on Java assertions and functional programming. */ +public class Tester { + private Map 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 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 findMethods(Class clss) { + List methods = new ArrayList(); +// 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 r = test.results; + for (String uid : r.keySet()) { + TesterStatus testStatus = r.get(uid); + System.out.println(testStatus); + } + } + + public static String usage() { + return "java " + Tester.class.getName() + " [test class name]"; + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/TesterStatus.java b/org.argeo.cms/src/org/argeo/cms/util/TesterStatus.java new file mode 100644 index 000000000..09ab432b2 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/TesterStatus.java @@ -0,0 +1,98 @@ +package org.argeo.cms.util; + +import java.io.Serializable; + +/** The status of a test. */ +public class TesterStatus implements Serializable { + private static final long serialVersionUID = 6272975746885487000L; + + private Boolean passed = null; + private final String uid; + private Throwable throwable = null; + + public TesterStatus(String uid) { + this.uid = uid; + } + + /** For cloning. */ + public TesterStatus(String uid, Boolean passed, Throwable throwable) { + this(uid); + this.passed = passed; + this.throwable = throwable; + } + + public synchronized Boolean isRunning() { + return passed == null; + } + + public synchronized Boolean isPassed() { + assert passed != null; + return passed; + } + + public synchronized Boolean isFailed() { + assert passed != null; + return !passed; + } + + public synchronized void setPassed() { + setStatus(true); + } + + public synchronized void setFailed() { + setStatus(false); + } + + public synchronized void setFailed(Throwable throwable) { + setStatus(false); + setThrowable(throwable); + } + + protected void setStatus(Boolean passed) { + if (this.passed != null) + throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")"); + this.passed = passed; + } + + protected void setThrowable(Throwable throwable) { + if (this.throwable != null) + throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")"); + this.throwable = throwable; + } + + public String getUid() { + return uid; + } + + public Throwable getThrowable() { + return throwable; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + // TODO Auto-generated method stub + return super.clone(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof TesterStatus) { + TesterStatus other = (TesterStatus) o; + // we don't check consistency for performance purposes + // this equals() is supposed to be used in collections or for transfer + return other.uid.equals(uid); + } + return false; + } + + @Override + public int hashCode() { + return uid.hashCode(); + } + + @Override + public String toString() { + return uid + "\t" + (passed ? "passed" : "failed"); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/Throughput.java b/org.argeo.cms/src/org/argeo/cms/util/Throughput.java new file mode 100644 index 000000000..4fc15f960 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/Throughput.java @@ -0,0 +1,82 @@ +package org.argeo.cms.util; + +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; + +/** A throughput, that is, a value per unit of time. */ +public class Throughput { + private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US); + + public enum Unit { + s, m, h, d + } + + private final Double value; + private final Unit unit; + + public Throughput(Double value, Unit unit) { + this.value = value; + this.unit = unit; + } + + public Throughput(Long periodMs, Long count, Unit unit) { + if (unit.equals(Unit.s)) + value = ((double) count * 1000d) / periodMs; + else if (unit.equals(Unit.m)) + value = ((double) count * 60d * 1000d) / periodMs; + else if (unit.equals(Unit.h)) + value = ((double) count * 60d * 60d * 1000d) / periodMs; + else if (unit.equals(Unit.d)) + value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs; + else + throw new IllegalArgumentException("Unsupported unit " + unit); + this.unit = unit; + } + + public Throughput(Double value, String unitStr) { + this(value, Unit.valueOf(unitStr)); + } + + public Throughput(String def) { + int index = def.indexOf('/'); + if (def.length() < 3 || index <= 0 || index != def.length() - 2) + throw new IllegalArgumentException( + def + " no a proper throughput definition" + " (should be /, e.g. 3.54/s or 1500/h"); + String valueStr = def.substring(0, index); + String unitStr = def.substring(index + 1); + try { + this.value = usNumberFormat.parse(valueStr).doubleValue(); + } catch (ParseException e) { + throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e); + } + this.unit = Unit.valueOf(unitStr); + } + + public Long asMsPeriod() { + if (unit.equals(Unit.s)) + return Math.round(1000d / value); + else if (unit.equals(Unit.m)) + return Math.round((60d * 1000d) / value); + else if (unit.equals(Unit.h)) + return Math.round((60d * 60d * 1000d) / value); + else if (unit.equals(Unit.d)) + return Math.round((24d * 60d * 60d * 1000d) / value); + else + throw new IllegalArgumentException("Unsupported unit " + unit); + } + + @Override + public String toString() { + return usNumberFormat.format(value) + '/' + unit; + } + + public Double getValue() { + return value; + } + + public Unit getUnit() { + return unit; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/util/package-info.java b/org.argeo.cms/src/org/argeo/cms/util/package-info.java new file mode 100644 index 000000000..5efc68afb --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/util/package-info.java @@ -0,0 +1,2 @@ +/** Generic Java utilities. */ +package org.argeo.cms.util; \ No newline at end of file diff --git a/org.argeo.init/.classpath b/org.argeo.init/.classpath index 71eb16789..4199cd3a3 100644 --- a/org.argeo.init/.classpath +++ b/org.argeo.init/.classpath @@ -2,7 +2,7 @@ - + diff --git a/org.argeo.init/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/Service.java index 9dcea49d2..cab85d02f 100644 --- a/org.argeo.init/src/org/argeo/init/Service.java +++ b/org.argeo.init/src/org/argeo/init/Service.java @@ -1,31 +1,34 @@ package org.argeo.init; +import java.io.IOException; +import java.io.InputStream; import java.lang.System.Logger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import org.argeo.init.logging.ThinLoggerFinder; +import org.argeo.init.osgi.OsgiBoot; import org.argeo.init.osgi.OsgiRuntimeContext; /** Configure and launch an Argeo service. */ -public class Service implements Runnable, AutoCloseable { - private final static Logger log = System.getLogger(Service.class.getName()); +public class Service { + private final static Logger logger = System.getLogger(Service.class.getName()); + + public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main"; private static RuntimeContext runtimeContext = null; protected Service(String[] args) { } - @Override - public void run() { - } - - @Override - public void close() throws Exception { - } - public static void main(String[] args) { - long pid = ProcessHandle.current().pid(); - log.log(Logger.Level.DEBUG, "Argeo Init starting with PID " + pid); + final long pid = ProcessHandle.current().pid(); + logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid); // shutdown on exit Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -37,19 +40,56 @@ public class Service implements Runnable, AutoCloseable { } } catch (Exception e) { e.printStackTrace(); - System.exit(1); + Runtime.getRuntime().halt(1); } }, "Runtime shutdown")); + // TODO use args as well + String dataArea = System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA); + String stateArea = System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA); + String configArea = System.getProperty(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA); + + if (configArea != null) { + Path configAreaPath = Paths.get(configArea); + Path additionalSystemPropertiesPath = configAreaPath.resolve("system.properties"); + if (Files.exists(additionalSystemPropertiesPath)) { + Properties properties = new Properties(); + try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) { + properties.load(in); + } catch (IOException e) { + logger.log(Logger.Level.ERROR, + "Cannot load additional system properties " + additionalSystemPropertiesPath, e); + } + + for (Object key : properties.keySet()) { + String currentValue = System.getProperty(key.toString()); + String value = properties.getProperty(key.toString()); + if (currentValue != null) { + if (!Objects.equals(value, currentValue)) + logger.log(Logger.Level.WARNING, "System property " + key + " already set with value " + + currentValue + " instead of " + value + ". Ignoring new value."); + } else { + System.setProperty(key.toString(), value); + } + } + ThinLoggerFinder.reloadConfiguration(); + } + } + Map config = new HashMap<>(); - config.put("osgi.framework.useSystemProperties", "true"); -// for (Object key : System.getProperties().keySet()) { -// config.put(key.toString(), System.getProperty(key.toString())); -// log.log(Logger.Level.DEBUG, key + "=" + System.getProperty(key.toString())); -// } + config.put(PROP_ARGEO_INIT_MAIN, "true"); + try { try { - OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext((Map) config); + if (stateArea != null) + config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea); + if (configArea != null) + config.put(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea); + if (dataArea != null) + config.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, dataArea); + // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true"); + + OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config); osgiRuntimeContext.run(); Service.runtimeContext = osgiRuntimeContext; Service.runtimeContext.waitForStop(0); @@ -63,10 +103,9 @@ public class Service implements Runnable, AutoCloseable { e.printStackTrace(); System.exit(1); } - log.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid); + logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid); } - public static RuntimeContext getRuntimeContext() { return runtimeContext; } diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java b/org.argeo.init/src/org/argeo/init/a2/A2Branch.java index 9713d01d3..cd8d89541 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Branch.java @@ -24,6 +24,10 @@ public class A2Branch implements Comparable { component.branches.put(id, this); } + public Iterable listModules(Object filter) { + return modules.values(); + } + A2Module getOrAddModule(Version version, Object locator) { if (modules.containsKey(version)) { A2Module res = modules.get(version); @@ -35,19 +39,19 @@ public class A2Branch implements Comparable { return new A2Module(this, version, locator); } - A2Module last() { + public A2Module last() { return modules.get(modules.lastKey()); } - A2Module first() { + public A2Module first() { return modules.get(modules.firstKey()); } - A2Component getComponent() { + public A2Component getComponent() { return component; } - String getId() { + public String getId() { return id; } diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Component.java b/org.argeo.init/src/org/argeo/init/a2/A2Component.java index 2b6814f6b..cc2f56471 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Component.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Component.java @@ -23,6 +23,10 @@ public class A2Component implements Comparable { contribution.components.put(id, this); } + public Iterable listBranches(Object filter) { + return branches.values(); + } + A2Branch getOrAddBranch(String branchId) { if (branches.containsKey(branchId)) return branches.get(branchId); @@ -36,15 +40,15 @@ public class A2Component implements Comparable { return module; } - A2Branch last() { + public A2Branch last() { return branches.get(branches.lastKey()); } - A2Contribution getContribution() { + public A2Contribution getContribution() { return contribution; } - String getId() { + public String getId() { return id; } diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java index 3d33b55e2..a4b720056 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java @@ -13,6 +13,8 @@ public class A2Contribution implements Comparable { final static String RUNTIME = "runtime"; final static String CLASSPATH = "classpath"; + final static String DEFAULT = "default"; + private final ProvisioningSource source; private final String id; @@ -30,6 +32,10 @@ public class A2Contribution implements Comparable { // context.contributions.put(id, this); } + public Iterable listComponents(Object filter) { + return components.values(); + } + A2Component getOrAddComponent(String componentId) { if (components.containsKey(componentId)) return components.get(componentId); diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Module.java b/org.argeo.init/src/org/argeo/init/a2/A2Module.java index b862c5435..0b6d3a91c 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Module.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Module.java @@ -7,7 +7,7 @@ import org.osgi.framework.Version; * Bundle-SymbolicName and Bundle-version. This is the * equivalent of the full coordinates of a Maven artifact version. */ -class A2Module implements Comparable { +public class A2Module implements Comparable { private final A2Branch branch; private final Version version; private final Object locator; @@ -19,11 +19,11 @@ class A2Module implements Comparable { branch.modules.put(version, this); } - A2Branch getBranch() { + public A2Branch getBranch() { return branch; } - Version getVersion() { + public Version getVersion() { return 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 index 388a85012..5c8329c85 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Source.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Source.java @@ -1,7 +1,18 @@ package org.argeo.init.a2; +import java.net.URI; + /** A provisioning source in A2 format. */ public interface A2Source extends ProvisioningSource { + /** Use standard a2 protocol, installing from source URL. */ final static String SCHEME_A2 = "a2"; + /** + * Use equinox-specific reference: installation, which does not copy the bundle + * content. + */ + final static String SCHEME_A2_REFERENCE = "a2+reference"; final static String DEFAULT_A2_URI = SCHEME_A2 + ":///"; + final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///"; + + URI getUri(); } diff --git a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java index f43a6162a..617e78878 100644 --- a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java +++ b/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java @@ -28,6 +28,12 @@ import org.osgi.framework.Version; public abstract class AbstractProvisioningSource implements ProvisioningSource { protected final Map contributions = Collections.synchronizedSortedMap(new TreeMap<>()); + private final boolean usingReference; + + public AbstractProvisioningSource(boolean usingReference) { + this.usingReference = usingReference; + } + public Iterable listContributions(Object filter) { return contributions.values(); } @@ -35,16 +41,25 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource { @Override public Bundle install(BundleContext bc, A2Module module) { try { - Path tempJar = null; - if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator())) - tempJar = toTempJar((Path) module.getLocator()); - Bundle bundle; - try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) { - bundle = bc.installBundle(module.getBranch().getCoordinates(), in); + Object locator = module.getLocator(); + if (usingReference && locator instanceof Path locatorPath) { + String referenceUrl = "reference:file:" + locatorPath.toString(); + Bundle bundle = bc.installBundle(referenceUrl); + return bundle; + } else { + + Path tempJar = null; + if (locator instanceof Path && Files.isDirectory((Path) locator)) + tempJar = toTempJar((Path) locator); + Bundle bundle; + try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) { + bundle = bc.installBundle(module.getBranch().getCoordinates(), in); + } + + if (tempJar != null) + Files.deleteIfExists(tempJar); + return bundle; } - if (tempJar != null) - Files.deleteIfExists(tempJar); - return bundle; } catch (BundleException | IOException e) { throw new A2Exception("Cannot install module " + module, e); } @@ -53,14 +68,21 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource { @Override public void update(Bundle bundle, A2Module module) { try { - Path tempJar = null; - if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator())) - tempJar = toTempJar((Path) module.getLocator()); - try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) { - bundle.update(in); + Object locator = module.getLocator(); + if (usingReference && locator instanceof Path) { + try (InputStream in = newInputStream(locator)) { + bundle.update(in); + } + } else { + Path tempJar = null; + if (locator instanceof Path && Files.isDirectory((Path) locator)) + tempJar = toTempJar((Path) locator); + try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) { + bundle.update(in); + } + if (tempJar != null) + Files.deleteIfExists(tempJar); } - if (tempJar != null) - Files.deleteIfExists(tempJar); } catch (BundleException | IOException e) { throw new A2Exception("Cannot update module " + module, e); } @@ -122,6 +144,25 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource { } + protected String[] readNameVersionFromModule(Path modulePath) { + Manifest manifest; + if (Files.isDirectory(modulePath)) { + manifest = findManifest(modulePath); + } else { + try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) { + manifest = in.getManifest(); + } catch (IOException e) { + throw new A2Exception("Cannot read manifest from " + modulePath, e); + } + } + String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION); + String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); + int semiColIndex = symbolicName.indexOf(';'); + if (semiColIndex >= 0) + symbolicName = symbolicName.substring(0, semiColIndex); + return new String[] { symbolicName, versionStr }; + } + protected String readVersionFromModule(Path modulePath) { Manifest manifest; if (Files.isDirectory(modulePath)) { @@ -155,6 +196,20 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource { return symbolicName; } + protected boolean isUsingReference() { + return usingReference; + } + + private InputStream newInputStream(Object locator) throws IOException { + if (locator instanceof Path) { + return Files.newInputStream((Path) locator); + } else if (locator instanceof URL) { + return ((URL) locator).openStream(); + } else { + throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass()); + } + } + private static Manifest findManifest(Path currentPath) { Path metaInfPath = currentPath.resolve("META-INF"); if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) { @@ -200,13 +255,4 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource { } - private InputStream newInputStream(Object locator) throws IOException { - if (locator instanceof Path) { - return Files.newInputStream((Path) locator); - } else if (locator instanceof URL) { - return ((URL) locator).openStream(); - } else { - throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass()); - } - } } diff --git a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java index 8a9e5e67f..12de4228b 100644 --- a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java +++ b/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java @@ -11,10 +11,15 @@ import org.argeo.init.osgi.OsgiBootUtils; import org.osgi.framework.Version; /** - * A provisioning source based on the linear classpath with which the JCM has + * A provisioning source based on the linear classpath with which the JVM has * been started. */ public class ClasspathSource extends AbstractProvisioningSource { + + public ClasspathSource() { + super(true); + } + void load() throws IOException { A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH); List classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator)); diff --git a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java index 067537d59..e0e2e437f 100644 --- a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java +++ b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java @@ -1,12 +1,16 @@ package org.argeo.init.a2; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.StringJoiner; +import java.util.TreeMap; import org.argeo.init.osgi.OsgiBootUtils; import org.osgi.framework.Version; @@ -14,27 +18,73 @@ import org.osgi.framework.Version; /** A file system {@link AbstractProvisioningSource} in A2 format. */ public class FsA2Source extends AbstractProvisioningSource implements A2Source { private final Path base; + private final Map variantsXOr; - public FsA2Source(Path base) { - super(); +// public FsA2Source(Path base) { +// this(base, new HashMap<>()); +// } + + public FsA2Source(Path base, Map variantsXOr, boolean usingReference) { + super(usingReference); this.base = base; + this.variantsXOr = new HashMap<>(variantsXOr); } void load() throws IOException { + SortedMap contributions = new TreeMap<>(); + DirectoryStream contributionPaths = Files.newDirectoryStream(base); - SortedSet contributions = new TreeSet<>(); contributions: for (Path contributionPath : contributionPaths) { if (Files.isDirectory(contributionPath)) { String contributionId = contributionPath.getFileName().toString(); if (A2Contribution.BOOT.equals(contributionId))// skip boot continue contributions; - A2Contribution contribution = getOrAddContribution(contributionId); - contributions.add(contribution); + if (contributionId.contains(".")) { + A2Contribution contribution = getOrAddContribution(contributionId); + contributions.put(contributionPath, contribution); + } else {// variants + Path variantPath = null; + // is it an explicit variant? + String variant = variantsXOr.get(contributionPath.getFileName().toString()); + if (variant != null) { + variantPath = contributionPath.resolve(variant); + } + + // is there a default variant? + if (variantPath == null) { + Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT); + if (Files.exists(defaultPath)) { + variantPath = defaultPath; + } + } + + if (variantPath == null) + continue contributions; + + // a variant was found, let's collect its contributions (also common ones in its + // parent) + for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) { + String variantContributionId = variantContributionPath.getFileName().toString(); + if (variantContributionId.contains(".")) { + A2Contribution contribution = getOrAddContribution(variantContributionId); + contributions.put(variantContributionPath, contribution); + } + } + for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) { + String variantContributionId = variantContributionPath.getFileName().toString(); + if (variantContributionId.contains(".")) { + A2Contribution contribution = getOrAddContribution(variantContributionId); + contributions.put(variantContributionPath, contribution); + } + } + } } } - for (A2Contribution contribution : contributions) { - DirectoryStream modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId())); + for (Path contributionPath : contributions.keySet()) { + String contributionId = contributionPath.getFileName().toString(); + A2Contribution contribution = getOrAddContribution(contributionId); + DirectoryStream modulePaths = Files.newDirectoryStream(contributionPath); modules: for (Path modulePath : modulePaths) { if (!Files.isDirectory(modulePath)) { // OsgiBootUtils.debug("Registering " + modulePath); @@ -43,21 +93,11 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source { String ext = moduleFileName.substring(lastDot + 1); if (!"jar".equals(ext)) continue modules; -// String moduleName = moduleFileName.substring(0, lastDot); -// if (moduleName.endsWith("-SNAPSHOT")) -// moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length()); -// int lastDash = moduleName.lastIndexOf('-'); -// String versionStr = moduleName.substring(lastDash + 1); -// String componentName = moduleName.substring(0, lastDash); - // if(versionStr.endsWith("-SNAPSHOT")) { - // versionStr = readVersionFromModule(modulePath); - // } Version version; -// try { -// version = new Version(versionStr); -// } catch (Exception e) { - String versionStr = readVersionFromModule(modulePath); - String componentName = readSymbolicNameFromModule(modulePath); + // TODO optimise? check attributes? + String[] nameVersion = readNameVersionFromModule(modulePath); + String componentName = nameVersion[0]; + String versionStr = nameVersion[1]; if (versionStr != null) { version = new Version(versionStr); } else { @@ -75,15 +115,46 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source { } - public static void main(String[] args) { + @Override + public URI getUri() { + URI baseUri = base.toUri(); try { - FsA2Source context = new FsA2Source(Paths.get( - "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi")); - context.load(); - context.asTree(); - } catch (Exception e) { - e.printStackTrace(); + if (baseUri.getScheme().equals("file")) { + String queryPart = ""; + if (!getVariantsXOr().isEmpty()) { + StringJoiner sj = new StringJoiner("&"); + for (String key : getVariantsXOr().keySet()) { + sj.add(key + "=" + getVariantsXOr().get(key)); + } + queryPart = sj.toString(); + } + return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart, + null); + } else { + throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme()); + } + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot build URI from " + baseUri, e); } } + protected Map getVariantsXOr() { + return variantsXOr; + } + +// public static void main(String[] args) { +// if (args.length == 0) +// throw new IllegalArgumentException("Usage: "); +// try { +// Map xOr = new HashMap<>(); +// xOr.put("osgi", "equinox"); +// xOr.put("swt", "rap"); +// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); +// context.load(); +// context.asTree(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + } diff --git a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java index 1657fad57..0313d20f3 100644 --- a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java +++ b/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java @@ -17,7 +17,7 @@ public class FsM2Source extends AbstractProvisioningSource { private final Path base; public FsM2Source(Path base) { - super(); + super(false); this.base = base; } diff --git a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java index 35fbee356..0064ab9ed 100644 --- a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java +++ b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java @@ -11,11 +11,12 @@ class OsgiContext extends AbstractProvisioningSource { private final BundleContext bc; public OsgiContext(BundleContext bc) { - super(); + super(false); this.bc = bc; } public OsgiContext() { + super(false); Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class); if (bundle == null) throw new IllegalArgumentException( diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java index 6012c34ee..cbb296f4c 100644 --- a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java +++ b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java @@ -1,7 +1,13 @@ package org.argeo.init.a2; +import static org.argeo.init.a2.A2Source.SCHEME_A2; +import static org.argeo.init.a2.A2Source.SCHEME_A2_REFERENCE; + import java.io.File; +import java.io.UnsupportedEncodingException; import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -10,6 +16,8 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -19,7 +27,6 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Version; -import org.osgi.framework.launch.Framework; import org.osgi.framework.wiring.FrameworkWiring; /** Loads provisioning sources into an OSGi context. */ @@ -48,14 +55,25 @@ public class ProvisioningManager { updatedBundles.add(bundle); } } - FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); - frameworkWiring.refreshBundles(updatedBundles); +// FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class); +// frameworkWiring.refreshBundles(updatedBundles); } public void registerSource(String uri) { try { URI u = new URI(uri); - if (A2Source.SCHEME_A2.equals(u.getScheme())) { + + // XOR + Map> properties = queryToMap(u); + Map xOr = new HashMap<>(); + for (String key : properties.keySet()) { + List lst = properties.get(key); + if (lst.size() != 1) + throw new IllegalArgumentException("Invalid XOR definitions in " + uri); + xOr.put(key, lst.get(0)); + } + + if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) { if (u.getHost() == null || "".equals(u.getHost())) { String baseStr = u.getPath(); if (File.separatorChar == '\\') {// MS Windows @@ -63,12 +81,19 @@ public class ProvisioningManager { } Path base = Paths.get(baseStr); if (Files.exists(base)) { - FsA2Source source = new FsA2Source(base); + FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme())); source.load(); addSource(source); OsgiBootUtils.info("Registered " + uri + " as source"); + } else { + OsgiBootUtils.debug("Source " + base + " does not exist, ignoring."); } + } else { + throw new UnsupportedOperationException( + "Remote installation is not yet supported, cannot add source " + u); } + } else { + throw new IllegalArgumentException("Unkown scheme: for source " + u); } } catch (Exception e) { throw new A2Exception("Cannot add source " + uri, e); @@ -172,29 +197,74 @@ public class ProvisioningManager { return updatedBundles; } - public static void main(String[] args) { - Map configuration = new HashMap<>(); - configuration.put("osgi.console", "2323"); - Framework framework = OsgiBootUtils.launch(configuration); + private static Map> queryToMap(URI uri) { + return queryToMap(uri.getQuery()); + } + + private static Map> queryToMap(String queryPart) { try { - ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); - FsA2Source context = new FsA2Source(Paths.get( - "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi")); - context.load(); - if (framework.getBundleContext().getBundles().length == 1) {// initial - pm.install(null); - } else { - pm.update(); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - // framework.stop(); - } catch (Exception e) { - e.printStackTrace(); + final Map> query_pairs = new LinkedHashMap>(); + 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()); + } + final String value = idx > 0 && pair.length() > idx + 1 + ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) + : null; + query_pairs.get(key).add(value); } + return query_pairs; + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e); } } +// public static void main(String[] args) { +// if (args.length == 0) +// throw new IllegalArgumentException("Usage: "); +// Map configuration = new HashMap<>(); +// configuration.put("osgi.console", "2323"); +// configuration.put("org.osgi.framework.bootdelegation", +// "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs"); +// Framework framework = OsgiBootUtils.launch(configuration); +// try { +// ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext()); +// Map xOr = new HashMap<>(); +// xOr.put("osgi", "equinox"); +// xOr.put("swt", "rap"); +// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr); +// context.load(); +// pm.addSource(context); +// if (framework.getBundleContext().getBundles().length == 1) {// initial +// pm.install(null); +// } else { +// pm.update(); +// } +// +// Thread.sleep(2000); +// +// Bundle[] bundles = framework.getBundleContext().getBundles(); +// Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName())); +// for (Bundle b : bundles) +// if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE) +// System.out.println(b.getSymbolicName() + " " + b.getVersion()); +// else +// System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")"); +// } catch (Exception e) { +// e.printStackTrace(); +// } finally { +// try { +// framework.stop(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// } + } diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java index 1bfab8e92..3ff5265e5 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java @@ -91,8 +91,13 @@ class ThinJavaUtilLogging { @Override public void publish(LogRecord record) { java.lang.System.Logger systemLogger = ThinLoggerFinder.getLogger(record.getLoggerName()); - systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(), - record.getThrown()); + if (record.getParameters() != null && record.getParameters().length > 0) { + systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(), + record.getParameters()); + } else { + systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(), + record.getThrown()); + } } @Override diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java b/org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java index f443b2e66..e60d22fba 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java @@ -21,17 +21,24 @@ public class ThinLoggerFinder extends LoggerFinder { public ThinLoggerFinder() { if (logging != null) throw new IllegalStateException("Only one logging can be initialised."); - init(); +// init(); } @Override public Logger getLogger(String name, Module module) { + lazyInit(); return logging.getLogger(name, module); } private static void init() { logging = new ThinLogging(); + reloadConfiguration(); + } + /** Reload configuration form system properties */ + public static void reloadConfiguration() { + if (logging == null) + return; Map configuration = new HashMap<>(); for (Object key : System.getProperties().keySet()) { Objects.requireNonNull(key); diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java index e21899394..5b2c93924 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java @@ -16,11 +16,9 @@ import java.util.ResourceBundle; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Flow; import java.util.concurrent.Flow.Subscription; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.SubmissionPublisher; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -39,8 +37,24 @@ class ThinLogging implements Consumer> { final static String DEFAULT_LEVEL_PROPERTY = "log"; final static String LEVEL_PROPERTY_PREFIX = DEFAULT_LEVEL_PROPERTY + "."; - final static String JOURNALD_PROPERTY = "argeo.logging.journald"; - final static String CALL_LOCATION_PROPERTY = "argeo.logging.callLocation"; + /** + * Whether logged event are only immediately printed to the standard output. + * Required for native images. + */ + final static String PROP_ARGEO_LOGGING_SYNCHRONOUS = "argeo.logging.synchronous"; + /** + * Whether to enable jounrald compatible output, either: auto (default), true, + * or false. + */ + final static String PROP_ARGEO_LOGGING_JOURNALD = "argeo.logging.journald"; + /** + * The level from which call location (that is, line number in Java code) will + * be searched (default is WARNING) + */ + final static String PROP_ARGEO_LOGGING_CALL_LOCATION_LEVEL = "argeo.logging.callLocationLevel"; + + final static String ENV_INVOCATION_ID = "INVOCATION_ID"; + final static String ENV_GIO_LAUNCHED_DESKTOP_FILE_PID = "GIO_LAUNCHED_DESKTOP_FILE_PID"; private final static AtomicLong nextEntry = new AtomicLong(0l); @@ -51,43 +65,37 @@ class ThinLogging implements Consumer> { private NavigableMap levels = new TreeMap<>(); private volatile boolean updatingConfiguration = false; - private final ExecutorService executor; private final LogEntryPublisher publisher; + private PrintStreamSubscriber synchronousSubscriber; private final boolean journald; private final Level callLocationLevel; - ThinLogging() { - executor = Executors.newCachedThreadPool((r) -> { - Thread t = new Thread(r); - t.setDaemon(true); - return t; - }); - publisher = new LogEntryPublisher(executor, Flow.defaultBufferSize()); - - PrintStreamSubscriber subscriber = new PrintStreamSubscriber(); - publisher.subscribe(subscriber); + private final boolean synchronous; + ThinLogging() { + publisher = new LogEntryPublisher(); + synchronous = Boolean.parseBoolean(System.getProperty(PROP_ARGEO_LOGGING_SYNCHRONOUS, "false")); + if (synchronous) { + synchronousSubscriber = new PrintStreamSubscriber(); + } else { + PrintStreamSubscriber subscriber = new PrintStreamSubscriber(); + publisher.subscribe(subscriber); + } Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown")); // initial default level - levels.put("", Level.WARNING); + levels.put(DEFAULT_LEVEL_NAME, Level.WARNING); // Logging system config // journald - -// Map env = new TreeMap<>(System.getenv()); -// for (String key : env.keySet()) { -// System.out.println(key + "=" + env.get(key)); -// } - - String journaldStr = System.getProperty(JOURNALD_PROPERTY, "auto"); + String journaldStr = System.getProperty(PROP_ARGEO_LOGGING_JOURNALD, "auto"); switch (journaldStr) { case "auto": - String systemdInvocationId = System.getenv("INVOCATION_ID"); + String systemdInvocationId = System.getenv(ENV_INVOCATION_ID); if (systemdInvocationId != null) {// in systemd - // check whether we are indirectly in a desktop app (e.g. eclipse) - String desktopFilePid = System.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID"); + // check whether we are indirectly in a desktop app (typically an IDE) + String desktopFilePid = System.getenv(ENV_GIO_LAUNCHED_DESKTOP_FILE_PID); if (desktopFilePid != null) { Long javaPid = ProcessHandle.current().pid(); if (!javaPid.toString().equals(desktopFilePid)) { @@ -110,10 +118,10 @@ class ThinLogging implements Consumer> { break; default: throw new IllegalArgumentException( - "Unsupported value '" + journaldStr + "' for property " + JOURNALD_PROPERTY); + "Unsupported value '" + journaldStr + "' for property " + PROP_ARGEO_LOGGING_JOURNALD); } - String callLocationStr = System.getProperty(CALL_LOCATION_PROPERTY, Level.WARNING.getName()); + String callLocationStr = System.getProperty(PROP_ARGEO_LOGGING_CALL_LOCATION_LEVEL, Level.WARNING.getName()); callLocationLevel = Level.valueOf(callLocationStr); } @@ -127,41 +135,29 @@ class ThinLogging implements Consumer> { } } - publisher.close(); - try { - // we ait a bit in order to make sure all messages are flushed - // TODO synchronize more efficiently - executor.awaitTermination(300, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // silent + if (!synchronous) { + publisher.close(); + try { + // we wait a bit in order to make sure all messages are flushed + // TODO synchronize more efficiently + // executor.awaitTermination(300, TimeUnit.MILLISECONDS); + ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // silent + } } } private Level computeApplicableLevel(String name) { Map.Entry entry = levels.floorEntry(name); assert entry != null; - return entry.getValue(); + if (name.startsWith(entry.getKey())) + return entry.getValue(); + else + return levels.get(DEFAULT_LEVEL_NAME);// default } -// private boolean isLoggable(String name, Level level) { -// Objects.requireNonNull(name); -// Objects.requireNonNull(level); -// -// if (updatingConfiguration) { -// synchronized (levels) { -// try { -// levels.wait(); -// // TODO make exit more robust -// } catch (InterruptedException e) { -// throw new IllegalStateException(e); -// } -// } -// } -// -// return level.getSeverity() >= computeApplicableLevel(name).getSeverity(); -// } - public Logger getLogger(String name, Module module) { if (!loggers.containsKey(name)) { ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name)); @@ -214,7 +210,6 @@ class ThinLogging implements Consumer> { updatingConfiguration = false; levels.notifyAll(); } - } Flow.Publisher> getLogEntryPublisher() { @@ -286,8 +281,19 @@ class ThinLogging implements Consumer> { // NOTE: this is the method called when logging a plain message without // exception, so it should be considered as a format only when args are not null - if (format.contains("{}"))// workaround for weird Jetty formatting - params = null; +// if (format.contains("{}"))// workaround for weird Jetty formatting +// params = null; + // TODO move this to slf4j wrapper? + if (format.contains("{}")) { + StringBuilder sb = new StringBuilder(); + String[] segments = format.split("\\{\\}"); + for (int i = 0; i < segments.length; i++) { + sb.append(segments[i]); + if (i != (segments.length - 1)) + sb.append("{" + i + "}"); + } + format = sb.toString(); + } String msg = params == null ? format : MessageFormat.format(format, params); publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread)); } @@ -315,6 +321,7 @@ class ThinLogging implements Consumer> { case "org.osgi.service.log.Logger": case "org.eclipse.osgi.internal.log.LoggerImpl": case "org.argeo.api.cms.CmsLog": + case "org.argeo.init.osgi.OsgiBootUtils": case "org.slf4j.impl.ArgeoLogger": case "org.argeo.cms.internal.osgi.CmsOsgiLogger": case "org.eclipse.jetty.util.log.Slf4jLog": @@ -342,8 +349,8 @@ class ThinLogging implements Consumer> { private class LogEntryPublisher extends SubmissionPublisher> { - private LogEntryPublisher(Executor executor, int maxBufferCapacity) { - super(executor, maxBufferCapacity); + private LogEntryPublisher() { + super(); } private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant, @@ -372,8 +379,13 @@ class ThinLogging implements Consumer> { logEntry.put(KEY_THREAD, thread.getName()); // should be unmodifiable for security reasons - if (!isClosed()) - submit(Collections.unmodifiableMap(logEntry)); + if (synchronous) { + assert synchronousSubscriber != null; + synchronousSubscriber.onNext(logEntry); + } else { + if (!isClosed()) + submit(Collections.unmodifiableMap(logEntry)); + } } } @@ -420,6 +432,8 @@ class ThinLogging implements Consumer> { private PrintStream err; private int writeToErrLevel = Level.WARNING.getSeverity(); + private Subscription subscription; + protected PrintStreamSubscriber() { this(System.out, System.err); } @@ -435,7 +449,8 @@ class ThinLogging implements Consumer> { @Override public void onSubscribe(Subscription subscription) { - subscription.request(Long.MAX_VALUE); + this.subscription = subscription; + this.subscription.request(1); } @Override @@ -446,6 +461,8 @@ class ThinLogging implements Consumer> { out.print(toPrint(item)); } // TODO flush for journald? + if (this.subscription != null) + this.subscription.request(1); } @Override @@ -507,10 +524,9 @@ class ThinLogging implements Consumer> { protected String toPrint(Map logEntry) { StringBuilder sb = new StringBuilder(); StringTokenizer st = new StringTokenizer((String) logEntry.get(KEY_MSG), "\r\n"); - assert st.hasMoreTokens(); // first line - String firstLine = st.nextToken(); + String firstLine = st.hasMoreTokens() ? st.nextToken() : ""; sb.append(firstLinePrefix(logEntry)); sb.append(firstLine); sb.append(firstLineSuffix(logEntry)); @@ -559,6 +575,16 @@ class ThinLogging implements Consumer> { logger.log(Logger.Level.INFO, "Log info"); logger.log(Logger.Level.WARNING, "Log warning"); logger.log(Logger.Level.ERROR, "Log exception", new Throwable()); + + try { + // we ait a bit in order to make sure all messages are flushed + // TODO synchronize more efficiently + // executor.awaitTermination(300, TimeUnit.MILLISECONDS); + ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // silent + } + } } diff --git a/org.argeo.init/src/org/argeo/init/osgi/Activator.java b/org.argeo.init/src/org/argeo/init/osgi/Activator.java index a4f5a371d..b85b248b9 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/Activator.java +++ b/org.argeo.init/src/org/argeo/init/osgi/Activator.java @@ -4,6 +4,7 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.util.Objects; +import org.argeo.init.Service; import org.argeo.init.logging.ThinLoggerFinder; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -18,34 +19,45 @@ public class Activator implements BundleActivator { // must be called first ThinLoggerFinder.lazyInit(); } - private Logger logger = System.getLogger(Activator.class.getName()); + private final static Logger logger = System.getLogger(Activator.class.getName()); private Long checkpoint = null; + + private boolean argeoInit = false; + /** Not null if we created it ourselves. */ private OsgiRuntimeContext runtimeContext; public void start(final BundleContext bundleContext) throws Exception { - if (runtimeContext == null) { - runtimeContext = new OsgiRuntimeContext(bundleContext); - } - logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator"); - - // admin thread + // The OSGi runtime was created by us, and therefore already initialized + argeoInit = Boolean.parseBoolean(bundleContext.getProperty(Service.PROP_ARGEO_INIT_MAIN)); + if (!argeoInit) { + if (runtimeContext == null) { + runtimeContext = new OsgiRuntimeContext(bundleContext); + logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator"); + } + + // admin thread // Thread adminThread = new AdminThread(bundleContext); // adminThread.start(); - // bootstrap + // bootstrap // OsgiBoot osgiBoot = new OsgiBoot(bundleContext); - if (checkpoint == null) { + if (checkpoint == null) { // osgiBoot.bootstrap(); - checkpoint = System.currentTimeMillis(); - } else { - runtimeContext.update(); - checkpoint = System.currentTimeMillis(); + checkpoint = System.currentTimeMillis(); + } else { + runtimeContext.update(); + checkpoint = System.currentTimeMillis(); + } } } public void stop(BundleContext context) throws Exception { - Objects.requireNonNull(runtimeContext); - runtimeContext.stop(context); + if (!argeoInit) { + Objects.nonNull(runtimeContext); + runtimeContext.stop(context); + runtimeContext = null; + } } + } diff --git a/org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java b/org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java index 73ab9afa7..863ee0084 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java +++ b/org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java @@ -25,7 +25,7 @@ class BundlesSet { dirPath = dirPath.substring("file:".length()); dir = new File(dirPath.replace('/', File.separatorChar)).getCanonicalPath(); - if (OsgiBootUtils.debug) + if (OsgiBootUtils.isDebug()) OsgiBootUtils.debug("Base dir: " + dir); } catch (IOException e) { throw new RuntimeException("Cannot convert to absolute path", e); diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java index 8fbe4b238..884461b12 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java @@ -38,52 +38,45 @@ import org.osgi.framework.wiring.FrameworkWiring; */ public class OsgiBoot implements OsgiBootConstants { public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start"; + public final static String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel"; public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources"; - public final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles"; - public final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl"; - public final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache"; - public final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl"; + @Deprecated + final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles"; + final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl"; + final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache"; + final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl"; // booleans - public final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug"; - // public final static String PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN = - // "argeo.osgi.boot.excludeSvn"; + @Deprecated + final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug"; - public final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile"; - public final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass"; - public final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs"; + final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile"; + final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass"; + final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs"; + @Deprecated public final static String DEFAULT_BASE_URL = "reference:file:"; - // public final static String EXCLUDES_SVN_PATTERN = "**/.svn/**"; + final static String DEFAULT_MAX_START_LEVEL = "32"; - // OSGi system properties + // OSGi standard properties final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel"; final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel"; - final static String INSTANCE_AREA_PROP = "osgi.instance.area"; - final static String CONFIGURATION_AREA_PROP = "osgi.configuration.area"; + public final static String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area"; + public final static String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; + public final static String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area"; + public final static String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties"; // Symbolic names - public final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.osgi.boot"; - public final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi"; - - /** Exclude svn metadata implicitely(a bit costly) */ - // private boolean excludeSvn = - // Boolean.valueOf(System.getProperty(PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN, - // "false")) - // .booleanValue(); - - /** Default is 10s */ - @Deprecated - private long defaultTimeout = 10000l; + final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.init"; + final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi"; private final BundleContext bundleContext; private final String localCache; - private final ProvisioningManager provisioningManager; /* - * INITIALIZATION + * INITIALISATION */ /** Constructor */ public OsgiBoot(BundleContext bundleContext) { @@ -97,15 +90,29 @@ public class OsgiBoot implements OsgiBootConstants { if (sources == null) { provisioningManager.registerDefaultSource(); } else { +// OsgiBootUtils.debug("Found sources " + sources); for (String source : sources.split(",")) { + int qmIndex = source.lastIndexOf('?'); + String queryPart = ""; + if (qmIndex >= 0) { + queryPart = source.substring(qmIndex); + source = source.substring(0, qmIndex); + } if (source.trim().equals(A2Source.DEFAULT_A2_URI)) { if (Files.exists(homePath)) provisioningManager.registerSource( - A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2"); - provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2"); - provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2"); + A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2" + queryPart); + provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2" + queryPart); + provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2" + queryPart); + } else if (source.trim().equals(A2Source.DEFAULT_A2_REFERENCE_URI)) { + if (Files.exists(homePath)) + provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + "://" + homePath.toString() + + "/.local/share/a2" + queryPart); + provisioningManager + .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/share/a2" + queryPart); + provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/share/a2" + queryPart); } else { - provisioningManager.registerSource(source); + provisioningManager.registerSource(source + queryPart); } } } @@ -118,18 +125,42 @@ public class OsgiBoot implements OsgiBootConstants { /* * HIGH-LEVEL METHODS */ - /** Bootstraps the OSGi runtime */ - public void bootstrap() { + /** + * Bootstraps the OSGi runtime using these properties, which MUST be consistent + * with {@link BundleContext#getProperty(String)}. If these properties are + * null, system properties are used instead. + */ + public void bootstrap(Map properties) { try { long begin = System.currentTimeMillis(); - System.out.println(); - String osgiInstancePath = bundleContext.getProperty(INSTANCE_AREA_PROP); - OsgiBootUtils - .info("OSGi bootstrap starting" + (osgiInstancePath != null ? " (" + osgiInstancePath + ")" : "")); + + // notify start + String osgiInstancePath = getProperty(PROP_OSGI_INSTANCE_AREA); + String osgiConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA); + String osgiSharedConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA); + OsgiBootUtils.info("OSGi bootstrap starting" // + + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") // + + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") // + + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") // + ); + + // legacy install bundles installUrls(getBundlesUrls()); installUrls(getDistributionUrls()); + + // A2 install bundles provisioningManager.install(null); - startBundles(); + + // Make sure fragments are properly considered by refreshing + refreshFramework(); + + // start bundles +// if (properties != null && !Boolean.parseBoolean(properties.get(PROP_OSGI_USE_SYSTEM_PROPERTIES))) + startBundles(properties); +// else +// startBundles(); + + // complete long duration = System.currentTimeMillis() - begin; OsgiBootUtils.info("OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s (" + duration + "ms), " + bundleContext.getBundles().length + " bundles"); @@ -139,7 +170,7 @@ public class OsgiBoot implements OsgiBootConstants { } // diagnostics - if (OsgiBootUtils.debug) { + if (OsgiBootUtils.isDebug()) { OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext); diagnostics.checkUnresolved(); Map> duplicatePackages = diagnostics.findPackagesExportedTwice(); @@ -159,6 +190,16 @@ public class OsgiBoot implements OsgiBootConstants { System.out.println(); } + /** + * Calls {@link #bootstrap(Map)} with null. + * + * @see #bootstrap(Map) + */ + @Deprecated + public void bootstrap() { + bootstrap(null); + } + public void update() { provisioningManager.update(); } @@ -181,7 +222,7 @@ public class OsgiBoot implements OsgiBootConstants { String url = (String) urls.get(i); installUrl(url, installedBundles); } - refreshFramework(); +// refreshFramework(); } /** Actually install the provided URL */ @@ -189,11 +230,11 @@ public class OsgiBoot implements OsgiBootConstants { try { if (installedBundles.containsKey(url)) { Bundle bundle = (Bundle) installedBundles.get(url); - if (OsgiBootUtils.debug) + if (OsgiBootUtils.isDebug()) debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url); } else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/") || url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) { - if (OsgiBootUtils.debug) + if (OsgiBootUtils.isDebug()) warn("Skip " + url); return; } else { @@ -201,7 +242,7 @@ public class OsgiBoot implements OsgiBootConstants { if (url.startsWith("http")) OsgiBootUtils .info("Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url); - else if (OsgiBootUtils.debug) + else if (OsgiBootUtils.isDebug()) OsgiBootUtils.debug( "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url); assert bundle.getSymbolicName() != null; @@ -250,7 +291,7 @@ public class OsgiBoot implements OsgiBootConstants { } else OsgiBootUtils.warn("Could not install bundle from " + url + ": " + message); } - if (OsgiBootUtils.debug && !message.contains(ALREADY_INSTALLED)) + if (OsgiBootUtils.isDebug() && !message.contains(ALREADY_INSTALLED)) e.printStackTrace(); } } @@ -258,16 +299,73 @@ public class OsgiBoot implements OsgiBootConstants { /* * START */ - public void startBundles() { - startBundles(System.getProperties()); + + /** + * Start bundles based on these properties. + * + * @see OsgiBoot#doStartBundles(Map) + */ + public void startBundles(Map properties) { + Map map = new TreeMap<>(); + // first use properties + if (properties != null) { + for (String key : properties.keySet()) { + String property = key; + if (property.startsWith(PROP_ARGEO_OSGI_START)) { + map.put(property, properties.get(property)); + } + } + } + // then try all start level until a maximum + int maxStartLevel = Integer.parseInt(getProperty(PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL)); + for (int i = 1; i <= maxStartLevel; i++) { + String key = PROP_ARGEO_OSGI_START + "." + i; + String value = getProperty(key); + if (value != null) + map.put(key, value); + + } + // finally, override with system properties + for (Object key : System.getProperties().keySet()) { + if (key.toString().startsWith(PROP_ARGEO_OSGI_START)) { + map.put(key.toString(), System.getProperty(key.toString())); + } + } + // start + doStartBundles(map); } + @Deprecated public void startBundles(Properties properties) { + Map map = new TreeMap<>(); + // first use properties + if (properties != null) { + for (Object key : properties.keySet()) { + String property = key.toString(); + if (property.startsWith(PROP_ARGEO_OSGI_START)) { + map.put(property, properties.get(property).toString()); + } + } + } + startBundles(map); + } + + /** Start bundle based on keys starting with {@link #PROP_ARGEO_OSGI_START}. */ + protected void doStartBundles(Map properties) { FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class); // default and active start levels from System properties - Integer defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4")); - Integer activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6")); + int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel(); + int defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4")); + int activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6")); + if (OsgiBootUtils.isDebug()) { + OsgiBootUtils.debug("OSGi default start level: " + + getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "") + ", using " + defaultStartLevel); + OsgiBootUtils.debug("OSGi active start level: " + getProperty(PROP_OSGI_STARTLEVEL, "") + + ", using " + activeStartLevel); + OsgiBootUtils.debug("Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: " + + initialStartLevel + ")"); + } SortedMap> startLevels = new TreeMap>(); computeStartLevels(startLevels, properties, defaultStartLevel); @@ -289,31 +387,58 @@ public class OsgiBoot implements OsgiBootConstants { } catch (BundleException e) { OsgiBootUtils.error("Cannot mark " + bsn + " as started", e); } - if (getDebug()) + if (OsgiBootUtils.isDebug()) OsgiBootUtils.debug(bsn + " starts at level " + level); } } } + + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("About to set framework start level to " + activeStartLevel + " ..."); + frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> { - if (getDebug()) - OsgiBootUtils.debug("Framework event: " + event); - int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel(); - int startLevel = frameworkStartLevel.getStartLevel(); - OsgiBootUtils.debug("Framework start level: " + startLevel + " (initial: " + initialStartLevel + ")"); + if (event.getType() == FrameworkEvent.ERROR) { + OsgiBootUtils.error("Start sequence failed", event.getThrowable()); + } else { + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("Framework started at level " + frameworkStartLevel.getStartLevel()); + } }); + +// // Start the framework level after level +// int currentStartLevel = frameworkStartLevel.getStartLevel(); +// stages: for (int stage = currentStartLevel + 1; stage <= activeStartLevel; stage++) { +// if (OsgiBootUtils.isDebug()) +// OsgiBootUtils.debug("Starting stage " + stage + "..."); +// final int nextStage = stage; +// final CompletableFuture stageCompleted = new CompletableFuture<>(); +// frameworkStartLevel.setStartLevel(nextStage, (FrameworkEvent event) -> { +// stageCompleted.complete(event); +// }); +// FrameworkEvent event; +// try { +// event = stageCompleted.get(); +// } catch (InterruptedException | ExecutionException e) { +// throw new IllegalStateException("Cannot continue start", e); +// } +// if (event.getThrowable() != null) { +// OsgiBootUtils.error("Stage " + nextStage + " failed, aborting start.", event.getThrowable()); +// break stages; +// } +// } } - private static void computeStartLevels(SortedMap> startLevels, Properties properties, + private static void computeStartLevels(SortedMap> startLevels, Map properties, Integer defaultStartLevel) { // default (and previously, only behaviour) - appendToStartLevels(startLevels, defaultStartLevel, properties.getProperty(PROP_ARGEO_OSGI_START, "")); + appendToStartLevels(startLevels, defaultStartLevel, properties.getOrDefault(PROP_ARGEO_OSGI_START, "")); // list argeo.osgi.start.* system properties - Iterator keys = properties.keySet().iterator(); + Iterator keys = properties.keySet().iterator(); final String prefix = PROP_ARGEO_OSGI_START + "."; while (keys.hasNext()) { - String key = keys.next().toString(); + String key = keys.next(); if (key.startsWith(prefix)) { Integer startLevel; String suffix = key.substring(prefix.length()); @@ -329,7 +454,7 @@ public class OsgiBoot implements OsgiBootConstants { startLevel = defaultStartLevel; // append bundle names - String bundleNames = properties.getProperty(key); + String bundleNames = properties.get(key); appendToStartLevels(startLevels, startLevel, bundleNames); } } @@ -350,109 +475,6 @@ public class OsgiBoot implements OsgiBootConstants { } } - /** - * Start the provided list of bundles - * - * @return whether all bundles are now in active state - * @deprecated - */ - @Deprecated - public boolean startBundles(List bundlesToStart) { - if (bundlesToStart.size() == 0) - return true; - - // used to monitor ACTIVE states - List startedBundles = new ArrayList(); - // used to log the bundles not found - List notFoundBundles = new ArrayList(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 notStarted = new ArrayList(); - while (!allStarted && (System.currentTimeMillis() - beginMonitor) < defaultTimeout) { - notStarted = new ArrayList(); - allStarted = true; - for (int i = 0; i < startedBundles.size(); i++) { - Bundle bundle = (Bundle) startedBundles.get(i); - // TODO check behaviour of lazs bundles - if (bundle.getState() != Bundle.ACTIVE) { - allStarted = false; - notStarted.add(bundle.getSymbolicName()); - } - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // silent - } - } - long duration = System.currentTimeMillis() - beginMonitor; - - if (!allStarted) - for (int i = 0; i < notStarted.size(); i++) - OsgiBootUtils.warn("Bundle '" + notStarted.get(i) + "' not ACTIVE after " + (duration / 1000) + "s"); - - return allStarted; - } - - /** Waits for a bundle to become active or resolved */ - @Deprecated - private void waitForBundleResolvedOrActive(long startBegin, Bundle bundle) throws Exception { - int originalState = bundle.getState(); - if ((originalState == Bundle.RESOLVED) || (originalState == Bundle.ACTIVE)) - return; - - String originalStateStr = OsgiBootUtils.stateAsString(originalState); - - int currentState = bundle.getState(); - while (!(currentState == Bundle.RESOLVED || currentState == Bundle.ACTIVE)) { - long now = System.currentTimeMillis(); - if ((now - startBegin) > defaultTimeout * 10) - throw new Exception("Bundle " + bundle.getSymbolicName() + " was not RESOLVED or ACTIVE after " - + (now - startBegin) + "ms (originalState=" + originalStateStr + ", currentState=" - + OsgiBootUtils.stateAsString(currentState) + ")"); - - try { - Thread.sleep(100l); - } catch (InterruptedException e) { - // silent - } - currentState = bundle.getState(); - } - } - /* * BUNDLE PATTERNS INSTALLATION */ @@ -478,13 +500,14 @@ public class OsgiBoot implements OsgiBootConstants { } /** Implements the path matching logic */ + @Deprecated public List getBundlesUrls(String baseUrl, String bundlePatterns) { List urls = new ArrayList(); if (bundlePatterns == null) return urls; // bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns); - if (OsgiBootUtils.debug) + if (OsgiBootUtils.isDebug()) debug(PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns); StringTokenizer st = new StringTokenizer(bundlePatterns, ","); @@ -572,16 +595,6 @@ public class OsgiBoot implements OsgiBootConstants { distributionBundle = new DistributionBundle(baseUrl, distributionUrl, localCache); } - // if (baseUrl != null && !(distributionUrl.startsWith("http") || - // distributionUrl.startsWith("file"))) { - // // relative url - // distributionBundle = new DistributionBundle(baseUrl, distributionUrl, - // localCache); - // } else { - // distributionBundle = new DistributionBundle(distributionUrl); - // if (baseUrl != null) - // distributionBundle.setBaseUrl(baseUrl); - // } distributionBundle.processUrl(); return distributionBundle.listUrls(); } @@ -597,7 +610,7 @@ public class OsgiBoot implements OsgiBootConstants { File[] files = baseDir.listFiles(); if (files == null) { - if (OsgiBootUtils.debug) + if (OsgiBootUtils.isDebug()) OsgiBootUtils.warn("Base dir " + baseDir + " has no children, exists=" + baseDir.exists() + ", isDirectory=" + baseDir.isDirectory()); return; @@ -630,13 +643,13 @@ public class OsgiBoot implements OsgiBootConstants { // FIXME recurse only if start matches ? match(matched, base, newCurrentPath, pattern); // } else { -// if (OsgiBootUtils.debug) +// if (OsgiBootUtils.isDebug()) // debug(newCurrentPath + " does not start match with " + pattern); // // } } else { boolean nonDirectoryOk = matcher.matches(Paths.get(newCurrentPath)); - if (OsgiBootUtils.debug) + if (OsgiBootUtils.isDebug()) debug(currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern); if (nonDirectoryOk) matched.add(relativeToFullPath(base, newCurrentPath)); @@ -647,10 +660,6 @@ public class OsgiBoot implements OsgiBootConstants { } } - protected void matchFile() { - - } - /* * LOW LEVEL UTILITIES */ @@ -693,6 +702,7 @@ public class OsgiBoot implements OsgiBootConstants { private void refreshFramework() { Bundle systemBundle = bundleContext.getBundle(0); FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class); + // TODO deal with refresh breaking native loading (e.g SWT) frameworkWiring.refreshBundles(null); } @@ -717,36 +727,8 @@ public class OsgiBoot implements OsgiBootConstants { * BEAN METHODS */ - public boolean getDebug() { - return OsgiBootUtils.debug; - } - - // public void setDebug(boolean debug) { - // this.debug = debug; - // } - public BundleContext getBundleContext() { return bundleContext; } - public String getLocalCache() { - return localCache; - } - - // public void setDefaultTimeout(long defaultTimeout) { - // this.defaultTimeout = defaultTimeout; - // } - - // public boolean isExcludeSvn() { - // return excludeSvn; - // } - // - // public void setExcludeSvn(boolean excludeSvn) { - // this.excludeSvn = excludeSvn; - // } - - /* - * INTERNAL CLASSES - */ - } diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java index d8efe8343..a782ac37b 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java @@ -1,12 +1,12 @@ package org.argeo.init.osgi; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; import java.util.ArrayList; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; import java.util.StringTokenizer; @@ -18,33 +18,26 @@ import org.osgi.framework.launch.FrameworkFactory; /** Utilities, mostly related to logging. */ public class OsgiBootUtils { - /** ISO8601 (as per log4j) and difference to UTC */ - private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS Z"); - - static boolean debug = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG) == null ? false - : !System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG).trim().equals("false"); + private final static Logger logger = System.getLogger(OsgiBootUtils.class.getName()); public static void info(Object obj) { - System.out.println("# OSGiBOOT # " + dateFormat.format(new Date()) + " # " + obj); + logger.log(Level.INFO, () -> Objects.toString(obj)); } public static void debug(Object obj) { - if (debug) - System.out.println("# OSGiBOOT DBG # " + dateFormat.format(new Date()) + " # " + obj); + logger.log(Level.TRACE, () -> Objects.toString(obj)); } public static void warn(Object obj) { - System.out.println("# OSGiBOOT WARN # " + dateFormat.format(new Date()) + " # " + obj); + logger.log(Level.WARNING, () -> Objects.toString(obj)); } public static void error(Object obj, Throwable e) { - System.err.println("# OSGiBOOT ERR # " + dateFormat.format(new Date()) + " # " + obj); - if (e != null) - e.printStackTrace(); + logger.log(Level.ERROR, () -> Objects.toString(obj), e); } public static boolean isDebug() { - return debug; + return logger.isLoggable(Level.TRACE); } public static String stateAsString(int state) { @@ -137,6 +130,7 @@ public class OsgiBootUtils { return framework; } + @Deprecated public static Map 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 index 39c42cbf3..cd2c80acb 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java @@ -38,8 +38,8 @@ public class OsgiBuilder { public OsgiBuilder() { // configuration.put("osgi.clean", "true"); - configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP)); - configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP)); + configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA)); + configuration.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA)); configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN)); } @@ -48,7 +48,7 @@ public class OsgiBuilder { framework = OsgiBootUtils.launch(configuration); BundleContext bc = framework.getBundleContext(); - String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP); + String osgiData = bc.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA); // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP); String osgiConf = framework.getDataFile("").getAbsolutePath(); if (OsgiBootUtils.isDebug()) diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java index 373e3d671..186577b4d 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java @@ -1,5 +1,6 @@ package org.argeo.init.osgi; +import java.lang.System.LoggerFinder; import java.util.Collections; import java.util.Hashtable; import java.util.Map; @@ -15,26 +16,28 @@ import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceRegistration; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; /** An OSGi runtime context. */ -public class OsgiRuntimeContext implements RuntimeContext { +public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable { private Map config; private Framework framework; private OsgiBoot osgiBoot; - @SuppressWarnings("rawtypes") - private ServiceRegistration loggingConfigurationSr; - @SuppressWarnings("rawtypes") - private ServiceRegistration logEntryPublisherSr; - + /** + * Constructor to use when the runtime context will create the OSGi + * {@link Framework}. + */ public OsgiRuntimeContext(Map config) { this.config = config; } - public OsgiRuntimeContext(BundleContext bundleContext) { + /** + * Constructor to use when the OSGi {@link Framework} has been created by other + * means. + */ + OsgiRuntimeContext(BundleContext bundleContext) { start(bundleContext); } @@ -55,16 +58,21 @@ public class OsgiRuntimeContext implements RuntimeContext { } public void start(BundleContext bundleContext) { + // preferences +// SystemRootPreferences systemRootPreferences = ThinPreferencesFactory.getInstance().getSystemRootPreferences(); +// bundleContext.registerService(AbstractPreferences.class, systemRootPreferences, new Hashtable<>()); + + // Make sure LoggerFinder has been searched for, since it is lazily loaded + LoggerFinder.getLoggerFinder(); + // logging - loggingConfigurationSr = bundleContext.registerService(Consumer.class, - ThinLoggerFinder.getConfigurationConsumer(), + bundleContext.registerService(Consumer.class, ThinLoggerFinder.getConfigurationConsumer(), new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration"))); - logEntryPublisherSr = bundleContext.registerService(Flow.Publisher.class, - ThinLoggerFinder.getLogEntryPublisher(), + bundleContext.registerService(Flow.Publisher.class, ThinLoggerFinder.getLogEntryPublisher(), new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher"))); osgiBoot = new OsgiBoot(bundleContext); - osgiBoot.bootstrap(); + osgiBoot.bootstrap(config); } @@ -113,4 +121,8 @@ public class OsgiRuntimeContext implements RuntimeContext { } + public Framework getFramework() { + return framework; + } + } diff --git a/org.argeo.init/src/org/argeo/init/osgi/log4j.properties b/org.argeo.init/src/org/argeo/init/osgi/log4j.properties deleted file mode 100644 index 1fcf25e3b..000000000 --- a/org.argeo.init/src/org/argeo/init/osgi/log4j.properties +++ /dev/null @@ -1,12 +0,0 @@ -log4j.rootLogger=WARN, console - -log4j.logger.org.argeo=INFO - -## Appenders -log4j.appender.console=org.apache.log4j.ConsoleAppender -log4j.appender.console.layout=org.apache.log4j.PatternLayout -log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n - -log4j.appender.development=org.apache.log4j.ConsoleAppender -log4j.appender.development.layout=org.apache.log4j.PatternLayout -log4j.appender.development.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n diff --git a/org.argeo.init/src/org/argeo/init/prefs/SystemRootPreferences.java b/org.argeo.init/src/org/argeo/init/prefs/SystemRootPreferences.java new file mode 100644 index 000000000..202100a49 --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/prefs/SystemRootPreferences.java @@ -0,0 +1,83 @@ +package org.argeo.init.prefs; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; + +public class SystemRootPreferences extends AbstractPreferences implements Consumer { + private CompletableFuture singleChild; + + protected SystemRootPreferences() { + super(null, ""); + } + + @Override + public void accept(AbstractPreferences t) { + this.singleChild.complete(t); + } + + /* + * ABSTRACT PREFERENCES + */ + + @Override + protected void putSpi(String key, String value) { + throw new UnsupportedOperationException(); + } + + @Override + protected String getSpi(String key) { + throw new UnsupportedOperationException(); + } + + @Override + protected void removeSpi(String key) { + throw new UnsupportedOperationException(); + } + + @Override + protected void removeNodeSpi() throws BackingStoreException { + throw new UnsupportedOperationException(); + } + + @Override + protected String[] keysSpi() throws BackingStoreException { + return new String[0]; + } + + /** Will block. */ + @Override + protected String[] childrenNamesSpi() throws BackingStoreException { + String childName; + try { + childName = singleChild.get().name(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot get child preferences name", e); + } + return new String[] { childName }; + } + + @Override + protected AbstractPreferences childSpi(String name) { + String childName; + try { + childName = singleChild.get().name(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot get child preferences name", e); + } + if (!childName.equals(name)) + throw new IllegalArgumentException("Child name is " + childName + ", not " + name); + return null; + } + + @Override + protected void syncSpi() throws BackingStoreException { + } + + @Override + protected void flushSpi() throws BackingStoreException { + } + +} diff --git a/org.argeo.init/src/org/argeo/init/prefs/ThinPreferencesFactory.java b/org.argeo.init/src/org/argeo/init/prefs/ThinPreferencesFactory.java new file mode 100644 index 000000000..8b3356c9c --- /dev/null +++ b/org.argeo.init/src/org/argeo/init/prefs/ThinPreferencesFactory.java @@ -0,0 +1,42 @@ +package org.argeo.init.prefs; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.prefs.Preferences; +import java.util.prefs.PreferencesFactory; + +public class ThinPreferencesFactory implements PreferencesFactory { + private static CompletableFuture INSTANCE = new CompletableFuture<>(); + + private SystemRootPreferences systemRootPreferences; + + public ThinPreferencesFactory() { + systemRootPreferences = new SystemRootPreferences(); + if (INSTANCE.isDone()) + throw new IllegalStateException( + "There is already a " + ThinPreferencesFactory.class.getName() + " instance."); + INSTANCE.complete(this); + } + + @Override + public Preferences systemRoot() { + return systemRootPreferences; + } + + @Override + public Preferences userRoot() { + throw new UnsupportedOperationException(); + } + + public SystemRootPreferences getSystemRootPreferences() { + return systemRootPreferences; + } + + public static ThinPreferencesFactory getInstance() { + try { + return INSTANCE.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot get " + ThinPreferencesFactory.class.getName() + " instance.", e); + } + } +} diff --git a/org.argeo.util/.classpath b/org.argeo.util/.classpath deleted file mode 100644 index 71eb16789..000000000 --- a/org.argeo.util/.classpath +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/org.argeo.util/.project b/org.argeo.util/.project deleted file mode 100644 index 171ff88dc..000000000 --- a/org.argeo.util/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.util - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.pde.PluginNature - - diff --git a/org.argeo.util/META-INF/.gitignore b/org.argeo.util/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/org.argeo.util/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/org.argeo.util/bnd.bnd b/org.argeo.util/bnd.bnd deleted file mode 100644 index 5f42f7786..000000000 --- a/org.argeo.util/bnd.bnd +++ /dev/null @@ -1,6 +0,0 @@ -Bundle-Activator: org.argeo.osgi.internal.EnterpriseActivator -Bundle-ActivationPolicy: lazy - -Import-Package: org.osgi.*;version=0.0.0,\ -!org.apache.commons.logging,\ -* diff --git a/org.argeo.util/build.properties b/org.argeo.util/build.properties deleted file mode 100644 index ae2abc5ff..000000000 --- a/org.argeo.util/build.properties +++ /dev/null @@ -1 +0,0 @@ -source.. = src/ \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java b/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java deleted file mode 100644 index bb495dd12..000000000 --- a/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.osgi.internal; - -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -/** - * Called to gather information about the OSGi runtime. Should not activate - * anything else that canonical monitoring services (not creating implicit - * APIs), which is the responsibility of higher levels. - */ -public class EnterpriseActivator implements BundleActivator { - - @Override - public void start(BundleContext context) throws Exception { - } - - @Override - public void stop(BundleContext context) throws Exception { - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java b/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java deleted file mode 100644 index 0fc4f32aa..000000000 --- a/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.osgi.metatype; - -import org.argeo.util.naming.SpecifiedName; -import org.osgi.service.metatype.AttributeDefinition; - -public interface EnumAD extends SpecifiedName, AttributeDefinition { - String name(); - - default Object getDefault() { - return null; - } - - @Override - default String getName() { - return name(); - } - - @Override - default String getID() { - return getClass().getName() + "." + name(); - } - - @Override - default String getDescription() { - return null; - } - - @Override - default int getCardinality() { - return 0; - } - - @Override - default int getType() { - return STRING; - } - - @Override - default String[] getOptionValues() { - return null; - } - - @Override - default String[] getOptionLabels() { - return null; - } - - @Override - default String validate(String value) { - return null; - } - - @Override - default String[] getDefaultValue() { - Object value = getDefault(); - if (value == null) - return null; - return new String[] { value.toString() }; - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java b/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java deleted file mode 100644 index 97c7d56e1..000000000 --- a/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java +++ /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> implements ObjectClassDefinition { - private final Class enumClass; - private String locale; - - public EnumOCD(Class 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 set = EnumSet.allOf(enumClass); - List attrs = new ArrayList<>(); - for (T key : set) - attrs.add((AttributeDefinition) key); - return attrs.toArray(new AttributeDefinition[attrs.size()]); - } - - @Override - public InputStream getIcon(int size) throws IOException { - return null; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java b/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java deleted file mode 100644 index bca5d1fda..000000000 --- a/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** OSGi metatype support. */ -package org.argeo.osgi.metatype; \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java b/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java deleted file mode 100644 index c0ec29000..000000000 --- a/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java +++ /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 map = Collections.synchronizedSortedMap(new TreeMap<>()); - - public SimpleProvisioningService() { - // update count - map.put(PROVISIONING_UPDATE_COUNT, 0); - } - - @Override - public Dictionary getInformation() { - return new Information(); - } - - @Override - public synchronized void setInformation(Dictionary info) { - map.clear(); - addInformation(info); - } - - @Override - public synchronized void addInformation(Dictionary info) { - Enumeration 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 { - - @Override - public int size() { - return map.size(); - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public Enumeration keys() { - Iterator it = map.keySet().iterator(); - return new Enumeration() { - - @Override - public boolean hasMoreElements() { - return it.hasNext(); - } - - @Override - public String nextElement() { - return it.next(); - } - - }; - } - - @Override - public Enumeration elements() { - Iterator it = map.values().iterator(); - return new Enumeration() { - - @Override - public boolean hasMoreElements() { - return it.hasNext(); - } - - @Override - public Object nextElement() { - return it.next(); - } - - }; - } - - @Override - public Object get(Object key) { - return map.get(key); - } - - @Override - public Object put(String key, Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public Object remove(Object key) { - throw new UnsupportedOperationException(); - } - - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java b/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java deleted file mode 100644 index 1859887e2..000000000 --- a/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** OSGi provisioning support. */ -package org.argeo.osgi.provisioning; \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java b/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java deleted file mode 100644 index 2dd94c654..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.argeo.osgi.transaction; - -/** JTA transaction status. */ -public class JtaStatusAdapter implements TransactionStatusAdapter { - private static final Integer STATUS_ACTIVE = 0; - private static final Integer STATUS_COMMITTED = 3; - private static final Integer STATUS_COMMITTING = 8; - private static final Integer STATUS_MARKED_ROLLBACK = 1; - private static final Integer STATUS_NO_TRANSACTION = 6; - private static final Integer STATUS_PREPARED = 2; - private static final Integer STATUS_PREPARING = 7; - private static final Integer STATUS_ROLLEDBACK = 4; - private static final Integer STATUS_ROLLING_BACK = 9; -// private static final Integer STATUS_UNKNOWN = 5; - - @Override - public Integer getActiveStatus() { - return STATUS_ACTIVE; - } - - @Override - public Integer getPreparingStatus() { - return STATUS_PREPARING; - } - - @Override - public Integer getMarkedRollbackStatus() { - return STATUS_MARKED_ROLLBACK; - } - - @Override - public Integer getPreparedStatus() { - return STATUS_PREPARED; - } - - @Override - public Integer getCommittingStatus() { - return STATUS_COMMITTING; - } - - @Override - public Integer getCommittedStatus() { - return STATUS_COMMITTED; - } - - @Override - public Integer getRollingBackStatus() { - return STATUS_ROLLING_BACK; - } - - @Override - public Integer getRolledBackStatus() { - return STATUS_ROLLEDBACK; - } - - @Override - public Integer getNoTransactionStatus() { - return STATUS_NO_TRANSACTION; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java deleted file mode 100644 index cf8a80b83..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.osgi.transaction; - -/** Internal unchecked rollback exception. */ -class SimpleRollbackException extends RuntimeException { - private static final long serialVersionUID = 8055601819719780566L; - - public SimpleRollbackException() { - super(); - } - - public SimpleRollbackException(Throwable cause) { - super(cause); - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java deleted file mode 100644 index e668b317e..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.argeo.osgi.transaction; - -import java.util.ArrayList; -import java.util.List; - -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; - -/** Simple implementation of an XA transaction. */ -class SimpleTransaction -//implements Transaction, Status -{ - private final Xid xid; - private T status; - private final List xaResources = new ArrayList(); - - private final SimpleTransactionManager transactionManager; - private TransactionStatusAdapter tsa; - - public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter tsa) { - this.tsa = tsa; - this.status = tsa.getActiveStatus(); - this.xid = new UuidXid(); - this.transactionManager = transactionManager; - } - - public synchronized void commit() -// throws RollbackException, HeuristicMixedException, HeuristicRollbackException, -// SecurityException, IllegalStateException, SystemException - { - status = tsa.getPreparingStatus(); - for (XAResource xaRes : xaResources) { - if (status.equals(tsa.getMarkedRollbackStatus())) - break; - try { - xaRes.prepare(xid); - } catch (XAException e) { - status = tsa.getMarkedRollbackStatus(); - error("Cannot prepare " + xaRes + " for " + xid, e); - } - } - if (status.equals(tsa.getMarkedRollbackStatus())) { - rollback(); - throw new SimpleRollbackException(); - } - status = tsa.getPreparedStatus(); - - status = tsa.getCommittingStatus(); - for (XAResource xaRes : xaResources) { - if (status.equals(tsa.getMarkedRollbackStatus())) - break; - try { - xaRes.commit(xid, false); - } catch (XAException e) { - status = tsa.getMarkedRollbackStatus(); - error("Cannot prepare " + xaRes + " for " + xid, e); - } - } - if (status.equals(tsa.getMarkedRollbackStatus())) { - rollback(); - throw new SimpleRollbackException(); - } - - // complete - status = tsa.getCommittedStatus(); - clearResources(XAResource.TMSUCCESS); - transactionManager.unregister(xid); - } - - public synchronized void rollback() -// throws IllegalStateException, SystemException - { - status = tsa.getRollingBackStatus(); - for (XAResource xaRes : xaResources) { - try { - xaRes.rollback(xid); - } catch (XAException e) { - error("Cannot rollback " + xaRes + " for " + xid, e); - } - } - - // complete - status = tsa.getRolledBackStatus(); - clearResources(XAResource.TMFAIL); - transactionManager.unregister(xid); - } - - public synchronized boolean enlistResource(XAResource xaRes) -// throws RollbackException, IllegalStateException, SystemException - { - if (xaResources.add(xaRes)) { - try { - xaRes.start(getXid(), XAResource.TMNOFLAGS); - return true; - } catch (XAException e) { - error("Cannot enlist " + xaRes, e); - return false; - } - } else - return false; - } - - public synchronized boolean delistResource(XAResource xaRes, int flag) -// throws IllegalStateException, SystemException - { - if (xaResources.remove(xaRes)) { - try { - xaRes.end(getXid(), flag); - } catch (XAException e) { - error("Cannot delist " + xaRes, e); - return false; - } - return true; - } else - return false; - } - - protected void clearResources(int flag) { - for (XAResource xaRes : xaResources) - try { - xaRes.end(getXid(), flag); - } catch (XAException e) { - error("Cannot end " + xaRes, e); - } - xaResources.clear(); - } - - protected void error(Object obj, Exception e) { - System.err.println(obj); - e.printStackTrace(); - } - - public synchronized T getStatus() -// throws SystemException - { - return status; - } - -// public void registerSynchronization(Synchronization sync) -// throws RollbackException, IllegalStateException, SystemException { -// throw new UnsupportedOperationException(); -// } - - public void setRollbackOnly() -// throws IllegalStateException, SystemException - { - status = tsa.getMarkedRollbackStatus(); - } - - @Override - public int hashCode() { - return xid.hashCode(); - } - - Xid getXid() { - return xid; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java deleted file mode 100644 index 3d4edfd17..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java +++ /dev/null @@ -1,214 +0,0 @@ -package org.argeo.osgi.transaction; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Callable; - -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; - -/** - * Simple implementation of an XA transaction manager. - */ -public class SimpleTransactionManager -// implements TransactionManager, UserTransaction - implements WorkControl, WorkTransaction { - private ThreadLocal> current = new ThreadLocal>(); - - private Map> knownTransactions = Collections - .synchronizedMap(new HashMap>()); - private TransactionStatusAdapter tsa = new JtaStatusAdapter(); -// private SyncRegistry syncRegistry = new SyncRegistry(); - - /* - * WORK IMPLEMENTATION - */ - @Override - public T required(Callable 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 transaction = new SimpleTransaction(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 getTransaction() -// throws SystemException - { - return getCurrent(); - } - - protected SimpleTransaction getCurrent() -// throws SystemException - { - SimpleTransaction 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 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 suspend() -// throws SystemException - { - SimpleTransaction transaction = getCurrent(); - current.remove(); - return transaction; - } - -// public TransactionSynchronizationRegistry getTsr() { -// return syncRegistry; -// } -// -// private class SyncRegistry implements TransactionSynchronizationRegistry { -// @Override -// public Object getTransactionKey() { -// try { -// SimpleTransaction transaction = getCurrent(); -// if (transaction == null) -// return null; -// return getCurrent().getXid(); -// } catch (SystemException e) { -// throw new IllegalStateException("Cannot get transaction key", e); -// } -// } -// -// @Override -// public void putResource(Object key, Object value) { -// throw new UnsupportedOperationException(); -// } -// -// @Override -// public Object getResource(Object key) { -// throw new UnsupportedOperationException(); -// } -// -// @Override -// public void registerInterposedSynchronization(Synchronization sync) { -// throw new UnsupportedOperationException(); -// } -// -// @Override -// public int getTransactionStatus() { -// try { -// return getStatus(); -// } catch (SystemException e) { -// throw new IllegalStateException("Cannot get status", e); -// } -// } -// -// @Override -// public boolean getRollbackOnly() { -// try { -// return getStatus() == Status.STATUS_MARKED_ROLLBACK; -// } catch (SystemException e) { -// throw new IllegalStateException("Cannot get status", e); -// } -// } -// -// @Override -// public void setRollbackOnly() { -// try { -// getCurrent().setRollbackOnly(); -// } catch (Exception e) { -// throw new IllegalStateException("Cannot set rollback only", e); -// } -// } -// -// } -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java b/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java deleted file mode 100644 index 87abceba4..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.osgi.transaction; - -/** Abstract the various approaches to represent transaction status. */ -public interface TransactionStatusAdapter { - T getActiveStatus(); - - T getPreparingStatus(); - - T getMarkedRollbackStatus(); - - T getPreparedStatus(); - - T getCommittingStatus(); - - T getCommittedStatus(); - - T getRollingBackStatus(); - - T getRolledBackStatus(); - - T getNoTransactionStatus(); -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java b/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java deleted file mode 100644 index 729aef8e5..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.argeo.osgi.transaction; - -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.UUID; - -import javax.transaction.xa.Xid; - -/** - * Implementation of {@link Xid} based on {@link UUID}, using max significant - * bits as global transaction id, and least significant bits as branch - * qualifier. - */ -public class UuidXid implements Xid, Serializable { - private static final long serialVersionUID = -5380531989917886819L; - public final static int FORMAT = (int) serialVersionUID; - - private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE; - - private final int format; - private final byte[] globalTransactionId; - private final byte[] branchQualifier; - private final String uuid; - private final int hashCode; - - public UuidXid() { - this(UUID.randomUUID()); - } - - public UuidXid(UUID uuid) { - this.format = FORMAT; - this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits()); - this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits()); - this.uuid = uuid.toString(); - this.hashCode = uuid.hashCode(); - } - - public UuidXid(Xid xid) { - this(xid.getFormatId(), xid.getGlobalTransactionId(), xid - .getBranchQualifier()); - } - - private UuidXid(int format, byte[] globalTransactionId, - byte[] branchQualifier) { - this.format = format; - this.globalTransactionId = globalTransactionId; - this.branchQualifier = branchQualifier; - this.uuid = bytesToUUID(globalTransactionId, branchQualifier) - .toString(); - this.hashCode = uuid.hashCode(); - } - - @Override - public int getFormatId() { - return format; - } - - @Override - public byte[] getGlobalTransactionId() { - return Arrays.copyOf(globalTransactionId, globalTransactionId.length); - } - - @Override - public byte[] getBranchQualifier() { - return Arrays.copyOf(branchQualifier, branchQualifier.length); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj instanceof UuidXid) { - UuidXid that = (UuidXid) obj; - return Arrays.equals(globalTransactionId, that.globalTransactionId) - && Arrays.equals(branchQualifier, that.branchQualifier); - } - if (obj instanceof Xid) { - Xid that = (Xid) obj; - return Arrays.equals(globalTransactionId, - that.getGlobalTransactionId()) - && Arrays - .equals(branchQualifier, that.getBranchQualifier()); - } - return uuid.equals(obj.toString()); - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return new UuidXid(format, globalTransactionId, branchQualifier); - } - - @Override - public String toString() { - return uuid; - } - - public UUID asUuid() { - return bytesToUUID(globalTransactionId, branchQualifier); - } - - public static byte[] uuidToBytes(long bits) { - ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG); - buffer.putLong(0, bits); - return buffer.array(); - } - - public static UUID bytesToUUID(byte[] most, byte[] least) { - if (most.length < BYTES_PER_LONG) - most = Arrays.copyOf(most, BYTES_PER_LONG); - if (least.length < BYTES_PER_LONG) - least = Arrays.copyOf(least, BYTES_PER_LONG); - ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG); - buffer.put(most, 0, BYTES_PER_LONG); - buffer.put(least, 0, BYTES_PER_LONG); - buffer.flip(); - return new UUID(buffer.getLong(), buffer.getLong()); - } - - // public static void main(String[] args) { - // UUID uuid = UUID.randomUUID(); - // System.out.println(uuid); - // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()), - // uuidToBytes(uuid.getLeastSignificantBits())); - // System.out.println(uuid); - // } -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java deleted file mode 100644 index f50f20870..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.osgi.transaction; - -import javax.transaction.xa.XAResource; - -/** - * A minimalistic interface similar to OSGi transaction context in order to - * register XA resources. - */ -public interface WorkContext { - void registerXAResource(XAResource resource, String recoveryId); -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java deleted file mode 100644 index 766809555..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.osgi.transaction; - -import java.util.concurrent.Callable; - -/** - * A minimalistic interface inspired by OSGi transaction control in order to - * commit units of work externally. - */ -public interface WorkControl { - T required(Callable work); - - void setRollbackOnly(); - - WorkContext getWorkContext(); -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java deleted file mode 100644 index 653390912..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.osgi.transaction; - -/** - * A minimalistic interface inspired by JTA user transaction in order to commit - * units of work externally. - */ -public interface WorkTransaction { - void begin(); - - void commit(); - - void rollback(); - - boolean isNoTransactionStatus(); -} diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java b/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java deleted file mode 100644 index 3d3756243..000000000 --- a/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Minimalistic and partial XA transaction manager implementation. */ -package org.argeo.osgi.transaction; \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java deleted file mode 100644 index e028e384e..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ /dev/null @@ -1,517 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.util.naming.LdapAttrs.objectClass; -import static org.argeo.util.naming.LdapObjs.extensibleObject; -import static org.argeo.util.naming.LdapObjs.inetOrgPerson; -import static org.argeo.util.naming.LdapObjs.organizationalPerson; -import static org.argeo.util.naming.LdapObjs.person; -import static org.argeo.util.naming.LdapObjs.top; - -import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; - -import javax.naming.InvalidNameException; -import javax.naming.NameNotFoundException; -import javax.naming.NamingEnumeration; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.osgi.transaction.WorkControl; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.Filter; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** Base class for a {@link UserDirectory}. */ -public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { - static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name"; - static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password"; - - private final Hashtable 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 indexedUserProperties = Arrays - // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(), - // LdapAttrs.cn.name() }); - - private final boolean scoped; - - private String memberAttributeId = "member"; - private List 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 props, boolean scoped) { - this.scoped = scoped; - properties = new Hashtable(); - for (Enumeration 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 getDirectGroups(LdapName dn); - - protected abstract Boolean daoHasRole(LdapName dn); - - protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException; - - protected abstract List 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 getAllRoles(DirectoryUser user) { - List allRoles = new ArrayList(); - if (user != null) { - collectRoles(user, allRoles); - allRoles.add(user); - } else - collectAnonymousRoles(allRoles); - return allRoles; - } - - private void collectRoles(DirectoryUser user, List 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 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 res = doGetRoles(f); - if (wc != null) { - for (Iterator 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 collectedUsers = new ArrayList(); - 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 collectedUsers) { - try { - Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")"); - List 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 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 getProperties() { - return properties; - } - - public Dictionary cloneProperties() { - return new Hashtable<>(properties); - } - - public void setExternalRoles(UserAdmin externalRoles) { - this.externalRoles = externalRoles; - } - -// public void setTransactionManager(TransactionManager transactionManager) { -// this.transactionManager = transactionManager; -// } - - public String getForcedPassword() { - return forcedPassword; - } - - public void setTransactionControl(WorkControl transactionControl) { - this.transactionControl = transactionControl; - } - - public WcXaResource getXaResource() { - return xaResource; - } - - public boolean isScoped() { - return scoped; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java deleted file mode 100644 index 05ba94889..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java +++ /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 systemRoles; - private final Set roles; - - public AggregatingAuthorization(String name, String displayName, Set systemRoles, String[] roles) { - this.name = new X500Principal(name).getName(); - this.displayName = displayName; - this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles)); - Set 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 res = new ArrayList(size); - res.addAll(systemRoles); - res.addAll(roles); - return res.toArray(new String[size]); - } - - @Override - public int hashCode() { - if (name == null) - return super.hashCode(); - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Authorization)) - return false; - Authorization that = (Authorization) obj; - if (name == null) - return that.getName() == null; - return name.equals(that.getName()); - } - - @Override - public String toString() { - return displayName; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java deleted file mode 100644 index c274ed97e..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ /dev/null @@ -1,277 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** - * Aggregates multiple {@link UserDirectory} and integrates them with system - * roles. - */ -public class AggregatingUserAdmin implements UserAdmin { - private final LdapName systemRolesBaseDn; - private final LdapName tokensBaseDn; - - // DAOs - private AbstractUserDirectory systemRoles = null; - private AbstractUserDirectory tokens = null; - private Map businessRoles = new HashMap(); - - // 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 res = new ArrayList(); - 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 res = new ArrayList(); - 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 sysRoles = new HashSet(); - 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 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 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 currentState() { -// Dictionary res = new Hashtable(); -// // res.put(NodeConstants.CN, NodeConstants.DEFAULT); -// for (LdapName name : businessRoles.keySet()) { -// AbstractUserDirectory userDirectory = businessRoles.get(name); -// String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString(); -// res.put(uri, ""); -// } -// return res; -// } - - public void destroy() { - for (LdapName name : businessRoles.keySet()) { - AbstractUserDirectory userDirectory = businessRoles.get(name); - destroy(userDirectory); - } - businessRoles.clear(); - businessRoles = null; - destroy(systemRoles); - systemRoles = null; - } - - private void destroy(AbstractUserDirectory userDirectory) { - preDestroy(userDirectory); - userDirectory.destroy(); - } - - protected void removeUserDirectory(LdapName baseDn) { - if (isSystemRolesBaseDn(baseDn)) - throw new UserDirectoryException("System roles cannot be removed "); - if (!businessRoles.containsKey(baseDn)) - throw new UserDirectoryException("No user directory registered for " + baseDn); - AbstractUserDirectory userDirectory = businessRoles.remove(baseDn); - destroy(userDirectory); - } - - /** - * Called before each user directory is destroyed, so that additional actions - * can be performed. - */ - protected void preDestroy(AbstractUserDirectory userDirectory) { - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java deleted file mode 100644 index 01db8be98..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java +++ /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 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 credentials) { - this.name = name; - this.credentials = credentials; - } - - public AuthenticatingUser(String name, char[] password) { - if (name == null) - throw new NullPointerException("Provided name cannot be null."); - this.name = name; - credentials = new Hashtable<>(); - credentials.put(SHARED_STATE_NAME, name); - byte[] pwd = DigestUtils.charsToBytes(password); - credentials.put(SHARED_STATE_PWD, pwd); - } - - @Override - public String getName() { - return name; - } - - @Override - public int getType() { - return User.USER; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public Dictionary getProperties() { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public Dictionary getCredentials() { - return credentials; - } - - @Override - public boolean hasCredential(String key, Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public String toString() { - return "Authenticating user " + name; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java deleted file mode 100644 index 511c2fede..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.spec.KeySpec; -import java.util.Arrays; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; - -/** Utilities around digests, mostly those related to passwords. */ -class DigestUtils { - final static String PASSWORD_SCHEME_SHA = "SHA"; - final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256"; - - static byte[] sha1(byte[] bytes) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA1"); - digest.update(bytes); - byte[] checksum = digest.digest(); - return checksum; - } catch (Exception e) { - throw new UserDirectoryException("Cannot SHA1 digest", e); - } - } - - static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations, - Integer keyLength) { - try { - if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) { - MessageDigest digest = MessageDigest.getInstance("SHA1"); - byte[] bytes = charsToBytes(password); - digest.update(bytes); - return digest.digest(); - } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) { - KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength); - - SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); - final int ITERATION_LENGTH = 4; - byte[] key = f.generateSecret(spec).getEncoded(); - byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length]; - byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray(); - if (iterationsArr.length < ITERATION_LENGTH) { - Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0); - System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length, - iterationsArr.length); - } else { - System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH); - } - System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length); - System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length); - return result; - } else { - throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme); - } - } catch (Exception e) { - throw new UserDirectoryException("Cannot digest", e); - } - } - - static char[] bytesToChars(Object obj) { - if (obj instanceof char[]) - return (char[]) obj; - if (!(obj instanceof byte[])) - throw new IllegalArgumentException(obj.getClass() + " is not a byte array"); - ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj); - CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer); - char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit()); - // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data - // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data - // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data - return res; - } - - static byte[] charsToBytes(char[] chars) { - CharBuffer charBuffer = CharBuffer.wrap(chars); - ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); - byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()); - // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data - // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data - return bytes; - } - - static String sha1str(String str) { - byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8)); - return encodeHexString(hash); - } - - final private static char[] hexArray = "0123456789abcdef".toCharArray(); - - /** - * From - * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to - * -a-hex-string-in-java - */ - public static String encodeHexString(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - private DigestUtils() { - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java deleted file mode 100644 index 7f8046313..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java +++ /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 getMemberNames(); -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java deleted file mode 100644 index 146b80578..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.osgi.useradmin; - -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; - -import org.osgi.service.useradmin.User; - -/** A user in a user directory. */ -interface DirectoryUser extends User { - LdapName getDn(); - - Attributes getAttributes(); - - void publishAttributes(Attributes modifiedAttributes); -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java deleted file mode 100644 index a9bc9417f..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.List; - -import javax.naming.InvalidNameException; -import javax.naming.NamingException; -import javax.naming.ldap.LdapName; - -import org.argeo.util.naming.DnsBrowser; -import org.argeo.util.naming.LdapAttrs; - -/** Free IPA specific conventions. */ -public class IpaUtils { - public final static String IPA_USER_BASE = "cn=users,cn=accounts"; - public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts"; - public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts"; - - private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase(); - - public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&" - + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true"; - - @Deprecated - static String domainToUserDirectoryConfigPath(String realm) { - return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm; - } - - public static void addIpaConfig(String realm, Dictionary 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 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 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 res = new Hashtable<>(); - res.put(UserAdminConf.uri.name(), uriStr.toString()); - addIpaConfig(kerberosRealm, res); - return res; - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java deleted file mode 100644 index ed69eb16b..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.util.Dictionary; -import java.util.Hashtable; - -import javax.naming.CommunicationException; -import javax.naming.Context; -import javax.naming.NameNotFoundException; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.InitialLdapContext; -import javax.naming.ldap.LdapName; - -import org.argeo.util.naming.LdapAttrs; - -/** A synchronized wrapper for a single {@link InitialLdapContext}. */ -// TODO implement multiple contexts and connection pooling. -class LdapConnection { - private InitialLdapContext initialLdapContext = null; - - LdapConnection(String url, Dictionary properties) { - try { - Hashtable connEnv = new Hashtable(); - 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 search(LdapName searchBase, String searchFilter, - SearchControls searchControls) throws NamingException { - NamingEnumeration results; - try { - results = getLdapContext().search(searchBase, searchFilter, searchControls); - } catch (CommunicationException e) { - reconnect(); - results = getLdapContext().search(searchBase, searchFilter, searchControls); - } - return results; - } - - public synchronized Attributes getAttributes(LdapName name) throws NamingException { - try { - return getLdapContext().getAttributes(name); - } catch (CommunicationException e) { - reconnect(); - return getLdapContext().getAttributes(name); - } - } - - synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException { - // make sure connection will work - reconnect(); - - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - if (!entryExists(dn)) - throw new UserDirectoryException("User to delete no found " + dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - if (entryExists(dn)) - throw new UserDirectoryException("User to create found " + dn); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn)) - throw new UserDirectoryException("User to modify not found " + dn); - } - - } - - protected boolean entryExists(LdapName dn) throws NamingException { - try { - return getAttributes(dn).size() != 0; - } catch (NameNotFoundException e) { - return false; - } - } - - synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException { - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - getLdapContext().destroySubcontext(dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - DirectoryUser user = wc.getNewUsers().get(dn); - getLdapContext().createSubcontext(dn, user.getAttributes()); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); - getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs); - } - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java deleted file mode 100644 index f8396085b..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java +++ /dev/null @@ -1,189 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.util.naming.LdapAttrs.objectClass; - -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.List; - -import javax.naming.AuthenticationNotSupportedException; -import javax.naming.Binding; -import javax.naming.Context; -import javax.naming.InvalidNameException; -import javax.naming.NameNotFoundException; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.ldap.LdapName; - -import org.osgi.framework.Filter; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** A user admin based on a LDAP server. */ -public class LdapUserAdmin extends AbstractUserDirectory { - private LdapConnection ldapConnection; - - public LdapUserAdmin(Dictionary properties) { - this(properties, false); - } - - public LdapUserAdmin(Dictionary 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 credentials = user.getCredentials(); - String username = (String) credentials.get(SHARED_STATE_USERNAME); - if (username == null) - username = user.getName(); - Dictionary 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 doGetRoles(Filter f) { - ArrayList res = new ArrayList(); - try { - String searchFilter = f != null ? f.toString() - : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "=" - + getGroupObjectClass() + "))"; - SearchControls searchControls = new SearchControls(); - searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); - - LdapName searchBase = getBaseDn(); - NamingEnumeration 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 getDirectGroups(LdapName dn) { - List directGroups = new ArrayList(); - try { - String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId() - + "=" + dn + "))"; - - SearchControls searchControls = new SearchControls(); - searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); - - LdapName searchBase = getBaseDn(); - NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); - - while (results.hasMoreElements()) { - SearchResult searchResult = (SearchResult) results.nextElement(); - directGroups.add(toDn(searchBase, searchResult)); - } - return directGroups; - } catch (Exception e) { - throw new UserDirectoryException("Cannot populate direct members of " + dn, e); - } - } - - @Override - protected void prepare(UserDirectoryWorkingCopy wc) { - try { - ldapConnection.prepareChanges(wc); - } catch (NamingException e) { - throw new UserDirectoryException("Cannot prepare LDAP", e); - } - } - - @Override - protected void commit(UserDirectoryWorkingCopy wc) { - try { - ldapConnection.commitChanges(wc); - } catch (NamingException e) { - throw new UserDirectoryException("Cannot commit LDAP", e); - } - } - - @Override - protected void rollback(UserDirectoryWorkingCopy wc) { - // prepare not impacting - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java deleted file mode 100644 index 15afe08b1..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Dictionary; -import java.util.List; - -import org.argeo.util.naming.LdapAttrs; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** Basic authorization. */ -class LdifAuthorization implements Authorization { - private final String name; - private final String displayName; - private final List allRoles; - - public LdifAuthorization(User user, List 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 props = user.getProperties(); - Object displayName = props.get(LdapAttrs.displayName); - if (displayName == null) - displayName = props.get(LdapAttrs.cn); - if (displayName == null) - displayName = props.get(LdapAttrs.uid); - if (displayName == null) - displayName = user.getName(); - if (displayName == null) - throw new UserDirectoryException("Cannot set display name for " + user); - return displayName.toString(); - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java deleted file mode 100644 index f4e558348..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java +++ /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 directMembers = new ArrayList(); - 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 null. - */ - 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 getMemberNames() { - Attribute memberAttribute = getAttributes().get(memberAttributeId); - if (memberAttribute == null) - return new ArrayList(); - try { - List roles = new ArrayList(); - NamingEnumeration values = memberAttribute.getAll(); - while (values.hasMore()) { - LdapName dn = new LdapName(values.next().toString()); - roles.add(dn); - } - return roles; - } catch (Exception e) { - throw new UserDirectoryException("Cannot get members", e); - } - } - - @Override - public Role[] getRequiredMembers() { - throw new UnsupportedOperationException(); - } - - @Override - public int getType() { - return GROUP; - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java deleted file mode 100644 index 135645a12..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java +++ /dev/null @@ -1,413 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static java.nio.charset.StandardCharsets.US_ASCII; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.ldap.LdapName; - -import org.argeo.util.naming.AuthPassword; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.SharedSecret; - -/** Directory user implementation */ -class LdifUser implements DirectoryUser { - private final AbstractUserDirectory userAdmin; - - private final LdapName dn; - - private final boolean frozen; - private Attributes publishedAttributes; - - private final AttributeDictionary properties; - private final AttributeDictionary credentials; - - LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) { - this(userAdmin, dn, attributes, false); - } - - private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) { - this.userAdmin = userAdmin; - this.dn = dn; - this.publishedAttributes = attributes; - properties = new AttributeDictionary(false); - credentials = new AttributeDictionary(true); - this.frozen = frozen; - } - - @Override - public String getName() { - return dn.toString(); - } - - @Override - public int getType() { - return USER; - } - - @Override - public Dictionary getProperties() { - return properties; - } - - @Override - public Dictionary 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 { - private final List effectiveKeys = new ArrayList(); - private final List attrFilter; - private final Boolean includeFilter; - - public AttributeDictionary(Boolean includeFilter) { - this.attrFilter = userAdmin.getCredentialAttributeIds(); - this.includeFilter = includeFilter; - try { - NamingEnumeration 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 keys() { - return Collections.enumeration(effectiveKeys); - } - - @Override - public Enumeration elements() { - final Iterator it = effectiveKeys.iterator(); - return new Enumeration() { - - @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 objectClasses = new HashSet(); - while (en.hasMore()) { - String objectClass = en.next().toString(); - objectClasses.add(objectClass); - } - - if (objectClasses.contains(userAdmin.getUserObjectClass())) - return userAdmin.getUserObjectClass(); - else if (objectClasses.contains(userAdmin.getGroupObjectClass())) - return userAdmin.getGroupObjectClass(); - else - return value; - } catch (NamingException e) { - throw new UserDirectoryException("Cannot get value for attribute " + key, e); - } - } - - @Override - public Object put(String key, Object value) { - if (key == null) { - // TODO persist to other sources (like PKCS12) - char[] password = DigestUtils.bytesToChars(value); - byte[] hashedPassword = sha1hash(password); - return put(LdapAttrs.userPassword.name(), hashedPassword); - } - if (key.startsWith("X-")) { - return put(LdapAttrs.authPassword.name(), value); - } - - userAdmin.checkEdit(); - if (!isEditing()) - startEditing(); - - if (!(value instanceof String || value instanceof byte[])) - throw new IllegalArgumentException("Value must be String or byte[]"); - - if (includeFilter && !attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " not included"); - else if (!includeFilter && attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " excluded"); - - try { - Attribute attribute = getModifiedAttributes().get(key.toString()); - // if (attribute == null) // block unit tests - attribute = new BasicAttribute(key.toString()); - if (value instanceof String && !isAsciiPrintable(((String) value))) - attribute.add(((String) value).getBytes(StandardCharsets.UTF_8)); - else - attribute.add(value); - Attribute previousAttribute = getModifiedAttributes().put(attribute); - if (previousAttribute != null) - return previousAttribute.get(); - else - return null; - } catch (NamingException e) { - throw new UserDirectoryException("Cannot get value for attribute " + key, e); - } - } - - @Override - public Object remove(Object key) { - userAdmin.checkEdit(); - if (!isEditing()) - startEditing(); - - if (includeFilter && !attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " not included"); - else if (!includeFilter && attrFilter.contains(key)) - throw new IllegalArgumentException("Key " + key + " excluded"); - - try { - Attribute attr = getModifiedAttributes().remove(key.toString()); - if (attr != null) - return attr.get(); - else - return null; - } catch (NamingException e) { - throw new UserDirectoryException("Cannot remove attribute " + key, e); - } - } - } - - private static boolean isAsciiPrintable(String str) { - if (str == null) { - return false; - } - int sz = str.length(); - for (int i = 0; i < sz; i++) { - if (isAsciiPrintable(str.charAt(i)) == false) { - return false; - } - } - return true; - } - - private static boolean isAsciiPrintable(char ch) { - return ch >= 32 && ch < 127; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java deleted file mode 100644 index 8b1206a72..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ /dev/null @@ -1,260 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.util.naming.LdapAttrs.objectClass; -import static org.argeo.util.naming.LdapObjs.inetOrgPerson; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Dictionary; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.List; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.naming.NameNotFoundException; -import javax.naming.NamingEnumeration; -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; - -import org.argeo.util.naming.LdifParser; -import org.argeo.util.naming.LdifWriter; -import org.osgi.framework.Filter; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** A user admin based on a LDIF files. */ -public class LdifUserAdmin extends AbstractUserDirectory { - private SortedMap users = new TreeMap(); - private SortedMap groups = new TreeMap(); - - public LdifUserAdmin(String uri, String baseDn) { - this(fromUri(uri, baseDn), false); - } - - public LdifUserAdmin(Dictionary properties) { - this(properties, false); - } - - protected LdifUserAdmin(Dictionary properties, boolean scoped) { - super(null, properties, scoped); - } - - public LdifUserAdmin(URI uri, Dictionary properties) { - super(uri, properties, false); - } - - @Override - protected AbstractUserDirectory scope(User user) { - Dictionary 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 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 fromUri(String uri, String baseDn) { - Hashtable res = new Hashtable(); - 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 allEntries = ldifParser.read(in); - for (LdapName key : allEntries.keySet()) { - Attributes attributes = allEntries.get(key); - // check for inconsistency - Set lowerCase = new HashSet(); - NamingEnumeration 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 doGetRoles(Filter f) { - ArrayList res = new ArrayList(); - 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 getDirectGroups(LdapName dn) { - List directGroups = new ArrayList(); - for (LdapName name : groups.keySet()) { - DirectoryGroup group = groups.get(name); - if (group.getMemberNames().contains(dn)) - directGroups.add(group.getDn()); - } - return directGroups; - } - - @Override - protected void prepare(UserDirectoryWorkingCopy wc) { - // delete - for (LdapName dn : wc.getDeletedUsers().keySet()) { - if (users.containsKey(dn)) - users.remove(dn); - else if (groups.containsKey(dn)) - groups.remove(dn); - else - throw new UserDirectoryException("User to delete not found " + dn); - } - // add - for (LdapName dn : wc.getNewUsers().keySet()) { - DirectoryUser user = wc.getNewUsers().get(dn); - if (users.containsKey(dn) || groups.containsKey(dn)) - throw new UserDirectoryException("User to create found " + dn); - else if (Role.USER == user.getType()) - users.put(dn, user); - else if (Role.GROUP == user.getType()) - groups.put(dn, (DirectoryGroup) user); - else - throw new UserDirectoryException("Unsupported role type " + user.getType() + " for new user " + dn); - } - // modify - for (LdapName dn : wc.getModifiedUsers().keySet()) { - Attributes modifiedAttrs = wc.getModifiedUsers().get(dn); - DirectoryUser user; - if (users.containsKey(dn)) - user = users.get(dn); - else if (groups.containsKey(dn)) - user = groups.get(dn); - else - throw new UserDirectoryException("User to modify no found " + dn); - user.publishAttributes(modifiedAttrs); - } - } - - @Override - protected void commit(UserDirectoryWorkingCopy wc) { - save(); - } - - @Override - protected void rollback(UserDirectoryWorkingCopy wc) { - init(); - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java deleted file mode 100644 index dd16e1a3b..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Dictionary; -import java.util.List; - -import javax.naming.NameNotFoundException; -import javax.naming.NamingException; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttributes; -import javax.naming.ldap.LdapName; - -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.Filter; -import org.osgi.service.useradmin.User; - -public class OsUserDirectory extends AbstractUserDirectory { - private final String osUsername = System.getProperty("user.name"); - private final LdapName osUserDn; - private final LdifUser osUser; - - public OsUserDirectory(URI uriArg, Dictionary 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 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 doGetRoles(Filter f) { - List res = new ArrayList<>(); - if (f == null || f.match(osUser.getProperties())) - res.add(osUser); - return res; - } - - @Override - protected AbstractUserDirectory scope(User user) { - throw new UnsupportedOperationException(); - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java deleted file mode 100644 index ad6bf8816..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.net.URISyntaxException; -import java.net.URL; -import java.security.NoSuchAlgorithmException; -import java.security.URIParameter; - -import javax.security.auth.Subject; -import javax.security.auth.login.Configuration; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -public class OsUserUtils { - private static String LOGIN_CONTEXT_USER_NIX = "USER_NIX"; - private static String LOGIN_CONTEXT_USER_NT = "USER_NT"; - - public static String getOsUsername() { - return System.getProperty("user.name"); - } - - public static LoginContext loginAsSystemUser(Subject subject) { - try { - URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader() - .getResource("org/argeo/osgi/useradmin/jaas-os.cfg"); - URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI()); - Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter); - LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject, - null, jaasConfiguration); - lc.login(); - return lc; - } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) { - throw new RuntimeException("Cannot login as system user", e); - } - } - - public static void main(String args[]) { - Subject subject = new Subject(); - LoginContext loginContext = loginAsSystemUser(subject); - System.out.println(subject); - try { - loginContext.logout(); - } catch (LoginException e) { - // silent - } - } - - private static boolean isWindows() { - return System.getProperty("os.name").startsWith("Windows"); - } - - private OsUserUtils() { - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java deleted file mode 100644 index 4b00e6c6a..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.util.naming.LdapAttrs.description; -import static org.argeo.util.naming.LdapAttrs.owner; - -import java.security.Principal; -import java.time.Instant; -import java.util.HashSet; -import java.util.Set; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.security.auth.Subject; - -import org.argeo.util.naming.NamingUtils; -import org.osgi.service.useradmin.Group; - -/** - * Canonically implements the Argeo token conventions. - */ -public class TokenUtils { - public static Set tokensUsed(Subject subject, String tokensBaseDn) { - Set res = new HashSet<>(); - for (Principal principal : subject.getPrincipals()) { - String name = principal.getName(); - if (name.endsWith(tokensBaseDn)) { - try { - LdapName ldapName = new LdapName(name); - String token = ldapName.getRdn(ldapName.size()).getValue().toString(); - res.add(token); - } catch (InvalidNameException e) { - throw new UserDirectoryException("Invalid principal " + principal, e); - } - } - } - return res; - } - - /** The user related to this token group */ - public static String userDn(Group tokenGroup) { - return (String) tokenGroup.getProperties().get(owner.name()); - } - - public static boolean isExpired(Group tokenGroup) { - return isExpired(tokenGroup, Instant.now()); - - } - - public static boolean isExpired(Group tokenGroup, Instant instant) { - String expiryDateStr = (String) tokenGroup.getProperties().get(description.name()); - if (expiryDateStr != null) { - Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr); - if (expiryDate.isBefore(instant)) { - return true; - } - } - return false; - } - -// private final String token; -// -// public TokenUtils(String token) { -// this.token = token; -// } -// -// public String getToken() { -// return token; -// } -// -// @Override -// public int hashCode() { -// return token.hashCode(); -// } -// -// @Override -// public boolean equals(Object obj) { -// if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token)) -// return true; -// return false; -// } -// -// @Override -// public String toString() { -// return "Token #" + hashCode(); -// } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java deleted file mode 100644 index 3631de40f..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java +++ /dev/null @@ -1,242 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; - -import javax.naming.Context; -import javax.naming.ldap.LdapName; - -import org.argeo.util.naming.NamingUtils; - -/** Properties used to configure user admins. */ -public enum UserAdminConf { - /** Base DN (cannot be configured externally) */ - baseDn("dc=example,dc=com"), - - /** URI of the underlying resource (cannot be configured externally) */ - uri("ldap://localhost:10389"), - - /** User objectClass */ - userObjectClass("inetOrgPerson"), - - /** Relative base DN for users */ - userBase("ou=People"), - - /** Groups objectClass */ - groupObjectClass("groupOfNames"), - - /** Relative base DN for users */ - groupBase("ou=Groups"), - - /** Read-only source */ - readOnly(null), - - /** Disabled source */ - disabled(null), - - /** Authentication realm */ - realm(null), - - /** Override all passwords with this value (typically for testing purposes) */ - forcedPassword(null); - - public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config"; - - public final static String SCHEME_LDAP = "ldap"; - public final static String SCHEME_LDAPS = "ldaps"; - public final static String SCHEME_FILE = "file"; - public final static String SCHEME_OS = "os"; - public final static String SCHEME_IPA = "ipa"; - - /** The default value. */ - private Object def; - - UserAdminConf(Object def) { - this.def = def; - } - - public Object getDefault() { - return def; - } - - /** - * For use as Java property. - * - * @deprecated use {@link #name()} instead - */ - @Deprecated - public String property() { - return name(); - } - - public String getValue(Dictionary properties) { - Object res = getRawValue(properties); - if (res == null) - return null; - return res.toString(); - } - - @SuppressWarnings("unchecked") - public T getRawValue(Dictionary 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 properties) { - StringBuilder query = new StringBuilder(); - - boolean first = true; -// for (Enumeration 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 uriAsProperties(String uriStr) { - try { - Hashtable res = new Hashtable(); - 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> query = NamingUtils.queryToMap(u); - for (String key : query.keySet()) { - UserAdminConf ldapProp = UserAdminConf.valueOf(key); - List 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 properties) { - String bDn = (String) properties.get(baseDn.name()); - if (bDn == null) - throw new UserDirectoryException("No baseDn in " + properties); - return DigestUtils.sha1str(bDn); - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java deleted file mode 100644 index ff80c5ac8..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.argeo.osgi.useradmin; - -import javax.naming.ldap.LdapName; -import javax.transaction.xa.XAResource; - -/** Information about a user directory. */ -public interface UserDirectory { - /** The base DN of all entries in this user directory */ - LdapName getBaseDn(); - - /** The related {@link XAResource} */ - XAResource getXaResource(); - - boolean isReadOnly(); - - boolean isDisabled(); - - String getUserObjectClass(); - - String getUserBase(); - - String getGroupObjectClass(); - - String getGroupBase(); -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java deleted file mode 100644 index 613d0fdf0..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.osgi.useradmin; - -import org.osgi.service.useradmin.UserAdmin; - -/** - * Exceptions related to Argeo's implementation of OSGi {@link UserAdmin} - * service. - */ -public class UserDirectoryException extends RuntimeException { - private static final long serialVersionUID = 1419352360062048603L; - - public UserDirectoryException(String message) { - super(message); - } - - public UserDirectoryException(String message, Throwable e) { - super(message, e); - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java deleted file mode 100644 index 0e25bdfa1..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java +++ /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 newUsers = new HashMap(); - private Map modifiedUsers = new HashMap(); - private Map deletedUsers = new HashMap(); - - 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 getNewUsers() { - return newUsers; - } - - public Map getDeletedUsers() { - return deletedUsers; - } - - public Map getModifiedUsers() { - return modifiedUsers; - } -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java b/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java deleted file mode 100644 index 1630b6bd3..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java +++ /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 workingCopies = new HashMap(); - private Xid editingXid = null; - private int transactionTimeout = 0; - - public WcXaResource(AbstractUserDirectory userDirectory) { - this.userDirectory = userDirectory; - } - - @Override - public synchronized void start(Xid xid, int flags) throws XAException { - if (editingXid != null) - throw new UserDirectoryException("Already editing " + editingXid); - UserDirectoryWorkingCopy wc = workingCopies.put(xid, new UserDirectoryWorkingCopy()); - if (wc != null) - throw new UserDirectoryException("There is already a working copy for " + xid); - this.editingXid = xid; - } - - @Override - public void end(Xid xid, int flags) throws XAException { - checkXid(xid); - } - - private UserDirectoryWorkingCopy wc(Xid xid) { - return workingCopies.get(xid); - } - - synchronized UserDirectoryWorkingCopy wc() { - if (editingXid == null) - return null; - UserDirectoryWorkingCopy wc = workingCopies.get(editingXid); - if (wc == null) - throw new UserDirectoryException("No working copy found for " + editingXid); - return wc; - } - - private synchronized void cleanUp(Xid xid) { - wc(xid).cleanUp(); - workingCopies.remove(xid); - editingXid = null; - } - - @Override - public int prepare(Xid xid) throws XAException { - checkXid(xid); - UserDirectoryWorkingCopy wc = wc(xid); - if (wc.noModifications()) - return XA_RDONLY; - try { - userDirectory.prepare(wc); - } catch (Exception e) { - e.printStackTrace(); - throw new XAException(XAException.XAER_RMERR); - } - return XA_OK; - } - - @Override - public void commit(Xid xid, boolean onePhase) throws XAException { - try { - checkXid(xid); - UserDirectoryWorkingCopy wc = wc(xid); - if (wc.noModifications()) - return; - if (onePhase) - userDirectory.prepare(wc); - userDirectory.commit(wc); - } catch (Exception e) { - e.printStackTrace(); - throw new XAException(XAException.XAER_RMERR); - } finally { - cleanUp(xid); - } - } - - @Override - public void rollback(Xid xid) throws XAException { - try { - checkXid(xid); - userDirectory.rollback(wc(xid)); - } catch (Exception e) { - e.printStackTrace(); - throw new XAException(XAException.XAER_RMERR); - } finally { - cleanUp(xid); - } - } - - @Override - public void forget(Xid xid) throws XAException { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isSameRM(XAResource xares) throws XAException { - return xares == this; - } - - @Override - public Xid[] recover(int flag) throws XAException { - return new Xid[0]; - } - - @Override - public int getTransactionTimeout() throws XAException { - return transactionTimeout; - } - - @Override - public boolean setTransactionTimeout(int seconds) throws XAException { - transactionTimeout = seconds; - return true; - } - - private void checkXid(Xid xid) throws XAException { - if (xid == null) - throw new XAException(XAException.XAER_OUTSIDE); - if (!xid.equals(xid)) - throw new XAException(XAException.XAER_NOTA); - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg b/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg deleted file mode 100644 index da04505a7..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg +++ /dev/null @@ -1,8 +0,0 @@ -USER_NIX { - com.sun.security.auth.module.UnixLoginModule requisite; -}; - -USER_NT { - com.sun.security.auth.module.NTLoginModule requisite; -}; - diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java b/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java deleted file mode 100644 index c108d2c55..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** LDAP and LDIF based OSGi useradmin implementation. */ -package org.argeo.osgi.useradmin; \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java deleted file mode 100644 index 31f1d4de6..000000000 --- a/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.osgi.util; - -import java.util.HashMap; -import java.util.Map; - -import org.osgi.resource.Namespace; -import org.osgi.resource.Requirement; -import org.osgi.resource.Resource; - -/** Simplify filtering resources. */ -public class FilterRequirement implements Requirement { - private String namespace; - private String filter; - - public FilterRequirement(String namespace, String filter) { - this.namespace = namespace; - this.filter = filter; - } - - @Override - public Resource getResource() { - return null; - } - - @Override - public String getNamespace() { - return namespace; - } - - @Override - public Map getDirectives() { - Map directives = new HashMap<>(); - directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter); - return directives; - } - - @Override - public Map getAttributes() { - return new HashMap<>(); - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java b/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java deleted file mode 100644 index 5a6760e0f..000000000 --- a/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.osgi.util; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Function; - -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; - -public class OnServiceRegistration implements Future { - private BundleContext ownBundleContext = FrameworkUtil.getBundle(OnServiceRegistration.class).getBundleContext(); - - private ServiceTracker st; - - private R result; - private boolean cancelled = false; - private Throwable exception; - - public OnServiceRegistration(Class clss, Function function) { - this(null, clss, function); - } - - public OnServiceRegistration(BundleContext bundleContext, Class clss, Function function) { - st = new ServiceTracker(bundleContext != null ? bundleContext : ownBundleContext, clss, null) { - - @Override - public T addingService(ServiceReference reference) { - T service = super.addingService(reference); - try { - if (result != null)// we only want the first one - return service; - result = function.apply(service); - return service; - } catch (Exception e) { - exception = e; - return service; - } finally { - close(); - } - } - }; - st.open(bundleContext == null); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - if (result != null || exception != null || cancelled) - return false; - st.close(); - cancelled = true; - return true; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public boolean isDone() { - return result != null || cancelled; - } - - @Override - public R get() throws InterruptedException, ExecutionException { - st.waitForService(0); - return tryGetResult(); - } - - @Override - public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - st.waitForService(TimeUnit.MILLISECONDS.convert(timeout, unit)); - if (result == null) - throw new TimeoutException("No result after " + timeout + " " + unit); - return tryGetResult(); - } - - protected R tryGetResult() throws ExecutionException, CancellationException { - if (cancelled) - throw new CancellationException(); - if (exception != null) - throw new ExecutionException(exception); - if (result == null)// this should not happen - try { - throw new IllegalStateException("No result available"); - } catch (Exception e) { - exception = e; - throw new ExecutionException(e); - } - return result; - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java b/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java deleted file mode 100644 index 7132b7c3f..000000000 --- a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.osgi.util; - -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ForkJoinPool; - -import org.argeo.util.register.Register; -import org.argeo.util.register.Singleton; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; - -public class OsgiRegister implements Register { - private final BundleContext bundleContext; - private Executor executor; - - private CompletableFuture shutdownStarting = new CompletableFuture(); - - public OsgiRegister(BundleContext bundleContext) { - this.bundleContext = bundleContext; - // TODO experiment with dedicated executors - this.executor = ForkJoinPool.commonPool(); - } - - @Override - public Singleton set(T obj, Class clss, Map attributes, Class... classes) { - CompletableFuture> srf = new CompletableFuture>(); - CompletableFuture postRegistration = CompletableFuture.supplyAsync(() -> { - List 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(attributes)); - srf.complete(sr); - return obj; - }, executor); - Singleton singleton = new Singleton(clss, postRegistration); - - shutdownStarting. // - thenCompose(singleton::prepareUnregistration). // - thenRunAsync(() -> { - try { - srf.get().unregister(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - }, executor); - return singleton; - } - - public void shutdown() { - shutdownStarting.complete(null); - } -} diff --git a/org.argeo.util/src/org/argeo/util/CsvParser.java b/org.argeo.util/src/org/argeo/util/CsvParser.java deleted file mode 100644 index b903f7722..000000000 --- a/org.argeo.util/src/org/argeo/util/CsvParser.java +++ /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 header, List 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 header = null; - if (!noHeader) { - String headerStr = bufferedReader.readLine(); - if (headerStr == null)// empty file - return; - lineCount++; - header = new ArrayList(); - 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 tokens = new ArrayList(); - 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 tokens, StringBuffer currStr, Boolean wasInquote) { - if (wasInquote) - currStr.append('\n'); - - char[] arr = str.toCharArray(); - boolean inQuote = wasInquote; - for (int i = 0; i < arr.length; i++) { - char c = arr[i]; - if (c == separator) { - if (!inQuote) { - tokens.add(currStr.toString()); -// currStr.delete(0, currStr.length()); - currStr.setLength(0); - currStr.trimToSize(); - } else { - // we don't remove separator that are in a quoted substring - // System.out - // .println("IN QUOTE, got a separator: [" + c + "]"); - currStr.append(c); - } - } else if (c == quote) { - if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) { - // case of double quote - currStr.append(quote); - i++; - } else {// standard - inQuote = inQuote ? false : true; - } - } else { - currStr.append(c); - } - } - - if (!inQuote) { - tokens.add(currStr.toString()); - // System.out.println("# TOKEN: " + currStr); - } - // if (inQuote) - // throw new ArgeoException("Missing quote at the end of the line " - // + str + " (parsed: " + tokens + ")"); - if (inQuote) - return true; - else - return false; - // return tokens; - } - - public char getSeparator() { - return separator; - } - - public synchronized void setSeparator(char separator) { - this.separator = separator; - } - - public char getQuote() { - return quote; - } - - public synchronized void setQuote(char quote) { - this.quote = quote; - } - - public Boolean getNoHeader() { - return noHeader; - } - - public synchronized void setNoHeader(Boolean noHeader) { - this.noHeader = noHeader; - } - - public Boolean getStrictLineAsLongAsHeader() { - return strictLineAsLongAsHeader; - } - - public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) { - this.strictLineAsLongAsHeader = strictLineAsLongAsHeader; - } - -} diff --git a/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java b/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java deleted file mode 100644 index 8eb6e9463..000000000 --- a/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java +++ /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 line); - - protected final void processLine(Integer lineNumber, List header, List tokens) { - if (header == null) - throw new IllegalArgumentException("Only CSV with header is supported"); - Map line = new HashMap(); - for (int i = 0; i < header.size(); i++) { - String key = header.get(i); - String value = null; - if (i < tokens.size()) - value = tokens.get(i); - line.put(key, value); - } - processLine(lineNumber, line); - } - -} diff --git a/org.argeo.util/src/org/argeo/util/CsvWriter.java b/org.argeo.util/src/org/argeo/util/CsvWriter.java deleted file mode 100644 index c3b3a3ad7..000000000 --- a/org.argeo.util/src/org/argeo/util/CsvWriter.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.argeo.util; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.Iterator; -import java.util.List; - -/** Write in CSV format. */ -public class CsvWriter { - private final Writer out; - - private char separator = ','; - private char quote = '\"'; - - /** - * Creates a CSV writer. - * - * @param out the stream to write to. Caller is responsible for closing it. - * - * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead. - * - */ - @Deprecated - public CsvWriter(OutputStream out) { - this.out = new OutputStreamWriter(out); - } - - /** - * Creates a CSV writer. - * - * @param out the stream to write to. Caller is responsible for closing it. - * @param encoding the encoding to use. - * - * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead. - */ - @Deprecated - public CsvWriter(OutputStream out, String encoding) { - try { - this.out = new OutputStreamWriter(out, encoding); - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException(e); - } - } - - /** - * Creates a CSV writer. - * - * @param out the stream to write to. Caller is responsible for closing it. - * @param charset the charset to use - */ - public CsvWriter(OutputStream out, Charset charset) { - this.out = new OutputStreamWriter(out, charset); - } - - /** - * Creates a CSV writer. - * - * @param out the stream to write to. Caller is responsible for closing it. - */ - public CsvWriter(Writer writer) { - this.out = writer; - } - - /** - * Write a CSV line. Also used to write a header if needed (this is transparent - * for the CSV writer): simply call it first, before writing the lines. - */ - public void writeLine(List tokens) { - try { - Iterator it = tokens.iterator(); - while (it.hasNext()) { - Object obj = it.next(); - writeToken(obj != null ? obj.toString() : null); - if (it.hasNext()) - out.write(separator); - } - out.write('\n'); - out.flush(); - } catch (IOException e) { - throw new RuntimeException("Could not write " + tokens, e); - } - } - - /** - * Write a CSV line. Also used to write a header if needed (this is transparent - * for the CSV writer): simply call it first, before writing the lines. - */ - public void writeLine(Object[] tokens) { - try { - for (int i = 0; i < tokens.length; i++) { - if (tokens[i] == null) { - writeToken(null); - } else { - writeToken(tokens[i].toString()); - } - if (i != (tokens.length - 1)) - out.write(separator); - } - out.write('\n'); - out.flush(); - } catch (IOException e) { - throw new RuntimeException("Could not write " + tokens, e); - } - } - - protected void writeToken(String token) throws IOException { - if (token == null) { - // TODO configure how to deal with null - out.write(""); - return; - } - // +2 for possible quotes, another +2 assuming there would be an already - // quoted string where quotes needs to be duplicated - // another +2 for safety - // we don't want to increase buffer size while writing - StringBuffer buf = new StringBuffer(token.length() + 6); - char[] arr = token.toCharArray(); - boolean shouldQuote = false; - for (char c : arr) { - if (!shouldQuote) { - if (c == separator) - shouldQuote = true; - if (c == '\n') - shouldQuote = true; - } - - if (c == quote) { - shouldQuote = true; - // duplicate quote - buf.append(quote); - } - - // generic case - buf.append(c); - } - - if (shouldQuote == true) - out.write(quote); - out.write(buf.toString()); - if (shouldQuote == true) - out.write(quote); - } - - public void setSeparator(char separator) { - this.separator = separator; - } - - public void setQuote(char quote) { - this.quote = quote; - } - -} diff --git a/org.argeo.util/src/org/argeo/util/DictionaryKeys.java b/org.argeo.util/src/org/argeo/util/DictionaryKeys.java deleted file mode 100644 index d17c86f96..000000000 --- a/org.argeo.util/src/org/argeo/util/DictionaryKeys.java +++ /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 { - private final Dictionary dictionary; - - public DictionaryKeys(Dictionary dictionary) { - this.dictionary = dictionary; - } - - @Override - public Iterator iterator() { - return new KeyIterator(dictionary.keys()); - } - - private static class KeyIterator implements Iterator { - private final Enumeration keys; - - KeyIterator(Enumeration keys) { - this.keys = keys; - } - - @Override - public boolean hasNext() { - return keys.hasMoreElements(); - } - - @Override - public String next() { - return keys.nextElement(); - } - - } -} diff --git a/org.argeo.util/src/org/argeo/util/DigestUtils.java b/org.argeo.util/src/org/argeo/util/DigestUtils.java deleted file mode 100644 index 38b4e7032..000000000 --- a/org.argeo.util/src/org/argeo/util/DigestUtils.java +++ /dev/null @@ -1,202 +0,0 @@ -package org.argeo.util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.MapMode; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** Utilities around cryptographic digests */ -public class DigestUtils { - public final static String MD5 = "MD5"; - public final static String SHA1 = "SHA1"; - public final static String SHA256 = "SHA-256"; - public final static String SHA512 = "SHA-512"; - - private static Boolean debug = false; - // TODO: make it configurable - private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB - - public static byte[] sha1(byte[]... bytes) { - try { - MessageDigest digest = MessageDigest.getInstance(SHA1); - for (byte[] arr : bytes) - digest.update(arr); - byte[] checksum = digest.digest(); - return checksum; - } catch (NoSuchAlgorithmException e) { - throw new UnsupportedOperationException("SHA1 is not avalaible", e); - } - } - - public static byte[] digestAsBytes(String algorithm, byte[]... bytes) { - try { - MessageDigest digest = MessageDigest.getInstance(algorithm); - for (byte[] arr : bytes) - digest.update(arr); - byte[] checksum = digest.digest(); - return checksum; - } catch (NoSuchAlgorithmException e) { - throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e); - } - } - - public static String digest(String algorithm, byte[]... bytes) { - return toHexString(digestAsBytes(algorithm, bytes)); - } - - public static String digest(String algorithm, InputStream in) { - try { - MessageDigest digest = MessageDigest.getInstance(algorithm); - // ReadableByteChannel channel = Channels.newChannel(in); - // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity); - // while (channel.read(bb) > 0) - // digest.update(bb); - byte[] buffer = new byte[byteBufferCapacity]; - int read = 0; - while ((read = in.read(buffer)) > 0) { - digest.update(buffer, 0, read); - } - - byte[] checksum = digest.digest(); - String res = toHexString(checksum); - return res; - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - StreamUtils.closeQuietly(in); - } - } - - public static String digest(String algorithm, File file) { - FileInputStream fis = null; - FileChannel fc = null; - try { - fis = new FileInputStream(file); - fc = fis.getChannel(); - - // Get the file's size and then map it into memory - int sz = (int) fc.size(); - ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz); - return digest(algorithm, bb); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e); - } finally { - StreamUtils.closeQuietly(fis); - if (fc.isOpen()) - try { - fc.close(); - } catch (IOException e) { - // silent - } - } - } - - protected static String digest(String algorithm, ByteBuffer bb) { - long begin = System.currentTimeMillis(); - try { - MessageDigest digest = MessageDigest.getInstance(algorithm); - digest.update(bb); - byte[] checksum = digest.digest(); - String res = toHexString(checksum); - long end = System.currentTimeMillis(); - if (debug) - System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s"); - return res; - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); - } - } - - public static String sha1hex(Path path) { - return digest(SHA1, path, byteBufferCapacity); - } - - public static String digest(String algorithm, Path path, long bufferSize) { - byte[] digest = digestAsBytes(algorithm, path, bufferSize); - return toHexString(digest); - } - - public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) { - long begin = System.currentTimeMillis(); - try { - MessageDigest md = MessageDigest.getInstance(algorithm); - FileChannel fc = FileChannel.open(file); - long fileSize = Files.size(file); - if (fileSize <= bufferSize) { - ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize); - md.update(bb); - } else { - long lastCycle = (fileSize / bufferSize) - 1; - long position = 0; - for (int i = 0; i <= lastCycle; i++) { - ByteBuffer bb; - if (i != lastCycle) { - bb = fc.map(MapMode.READ_ONLY, position, bufferSize); - position = position + bufferSize; - } else { - bb = fc.map(MapMode.READ_ONLY, position, fileSize - position); - position = fileSize; - } - md.update(bb); - } - } - long end = System.currentTimeMillis(); - if (debug) - System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s"); - return md.digest(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e); - } catch (IOException e) { - throw new RuntimeException("Cannot digest " + file + " with algorithm " + algorithm, e); - } - } - - public static void main(String[] args) { - File file; - if (args.length > 0) - file = new File(args[0]); - else { - System.err.println("Usage: []" + " (see http://java.sun.com/j2se/1.5.0/" - + "docs/guide/security/CryptoSpec.html#AppA)"); - return; - } - - if (args.length > 1) { - String algorithm = args[1]; - System.out.println(digest(algorithm, file)); - } else { - String algorithm = "MD5"; - System.out.println(algorithm + ": " + digest(algorithm, file)); - algorithm = "SHA"; - System.out.println(algorithm + ": " + digest(algorithm, file)); - System.out.println(algorithm + ": " + sha1hex(file.toPath())); - algorithm = "SHA-256"; - System.out.println(algorithm + ": " + digest(algorithm, file)); - algorithm = "SHA-512"; - System.out.println(algorithm + ": " + digest(algorithm, file)); - } - } - - final private static char[] hexArray = "0123456789abcdef".toCharArray(); - - /** Converts a byte array to an hex String. */ - public static String toHexString(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - -} diff --git a/org.argeo.util/src/org/argeo/util/DirH.java b/org.argeo.util/src/org/argeo/util/DirH.java deleted file mode 100644 index 013897d23..000000000 --- a/org.argeo.util/src/org/argeo/util/DirH.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.argeo.util; - -import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.Charset; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** Hashes the hashes of the files in a directory. */ -public class DirH { - - private final static Charset charset = Charset.forName("UTF-16"); - private final static long bufferSize = 200 * 1024 * 1024; - private final static String algorithm = "SHA"; - - private final static byte EOL = (byte) '\n'; - private final static byte SPACE = (byte) ' '; - - private final int hashSize; - - private final byte[][] hashes; - private final byte[][] fileNames; - private final byte[] digest; - private final byte[] dirName; - - /** - * @param dirName can be null or empty - */ - private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) { - if (hashes.length != fileNames.length) - throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names"); - this.hashes = hashes; - this.fileNames = fileNames; - this.dirName = dirName == null ? new byte[0] : dirName; - if (hashes.length == 0) {// empty dir - hashSize = 20; - // FIXME what is the digest of an empty dir? - digest = new byte[hashSize]; - Arrays.fill(digest, SPACE); - return; - } - hashSize = hashes[0].length; - for (int i = 0; i < hashes.length; i++) { - if (hashes[i].length != hashSize) - throw new IllegalArgumentException( - "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length); - } - - try { - MessageDigest md = MessageDigest.getInstance(algorithm); - for (int i = 0; i < hashes.length; i++) { - md.update(this.hashes[i]); - md.update(SPACE); - md.update(this.fileNames[i]); - md.update(EOL); - } - digest = md.digest(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Cannot digest", e); - } - } - - public void print(PrintStream out) { - out.print(DigestUtils.toHexString(digest)); - if (dirName.length > 0) { - out.print(' '); - out.print(new String(dirName, charset)); - } - out.print('\n'); - for (int i = 0; i < hashes.length; i++) { - out.print(DigestUtils.toHexString(hashes[i])); - out.print(' '); - out.print(new String(fileNames[i], charset)); - out.print('\n'); - } - } - - public static DirH digest(Path dir) { - try (DirectoryStream files = Files.newDirectoryStream(dir)) { - List hs = new ArrayList(); - List fNames = new ArrayList<>(); - for (Path file : files) { - if (!Files.isDirectory(file)) { - byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize); - hs.add(digest); - fNames.add(file.getFileName().toString()); - } - } - - byte[][] fileNames = new byte[fNames.size()][]; - for (int i = 0; i < fNames.size(); i++) { - fileNames[i] = fNames.get(i).getBytes(charset); - } - byte[][] hashes = hs.toArray(new byte[hs.size()][]); - return new DirH(hashes, fileNames, dir.toString().getBytes(charset)); - } catch (IOException e) { - throw new RuntimeException("Cannot digest " + dir, e); - } - } - - public static void main(String[] args) { - try { - DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/")); - dirH.print(System.out); - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/org.argeo.util/src/org/argeo/util/FsUtils.java b/org.argeo.util/src/org/argeo/util/FsUtils.java deleted file mode 100644 index b317f4bc9..000000000 --- a/org.argeo.util/src/org/argeo/util/FsUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.util; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; - -/** Utilities around the standard Java file abstractions. */ -public class FsUtils { - /** - * Deletes this path, recursively if needed. Does nothing if the path does not - * exist. - */ - public static void delete(Path path) { - try { - if (!Files.exists(path)) - return; - Files.walkFileTree(path, new SimpleFileVisitor() { - @Override - public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException { - if (e != null) - throw e; - Files.delete(directory); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - throw new RuntimeException("Cannot delete " + path, e); - } - } - - /** Singleton. */ - private FsUtils() { - } - -} diff --git a/org.argeo.util/src/org/argeo/util/LangUtils.java b/org.argeo.util/src/org/argeo/util/LangUtils.java deleted file mode 100644 index 162294537..000000000 --- a/org.argeo.util/src/org/argeo/util/LangUtils.java +++ /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 map(String key, Object value) { - assert key != null; - HashMap 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 dict(String key, Object value) { - assert key != null; - Hashtable props = new Hashtable<>(); - if (value != null) - props.put(key, value); - return props; - } - - /** @deprecated Use {@link #dict(String, Object)} instead. */ - @Deprecated - public static Dictionary dico(String key, Object value) { - return dict(key, value); - } - - /** Converts a {@link Dictionary} to a {@link Map} of strings. */ - public static Map dictToStringMap(Dictionary properties) { - if (properties == null) { - return null; - } - Map res = new HashMap<>(properties.size()); - Enumeration 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 - * null if not found. - */ - public static String get(Map 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 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 keys(Dictionary props) { - assert props != null; - return new DictionaryKeys(props); - } - - static String toJson(Dictionary props) { - return toJson(props, false); - } - - static String toJson(Dictionary props, boolean pretty) { - StringBuilder sb = new StringBuilder(); - sb.append('{'); - if (pretty) - sb.append('\n'); - Enumeration 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 props, Path path) throws IOException { - if (props == null) - throw new IllegalArgumentException("Props cannot be null"); - Properties toStore = new Properties(); - for (Enumeration 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 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 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 loadFromProperties(Path path) throws IOException { - Properties toLoad = new Properties(); - try (InputStream in = Files.newInputStream(path)) { - toLoad.load(in); - } - Dictionary res = new Hashtable(); - 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 null. - */ - public static List toStringList(Object value) { - List 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, starts with a line - * return) without all the stack - */ - public static String chainCausesMessages(Throwable t) { - StringBuffer buf = new StringBuffer(); - chainCauseMessage(buf, t); - return buf.toString(); - } - - /** Recursive chaining of messages */ - private static void chainCauseMessage(StringBuffer buf, Throwable t) { - buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage()); - if (t.getCause() != null) - chainCauseMessage(buf, t.getCause()); - } - - /* - * TIME - */ - /** Formats time elapsed since start. */ - public static String since(ZonedDateTime start) { - ZonedDateTime now = ZonedDateTime.now(); - return duration(start, now); - } - - /** Formats a duration. */ - public static String duration(Temporal start, Temporal end) { - long count = ChronoUnit.DAYS.between(start, end); - if (count != 0) - return count > 1 ? count + " days" : count + " day"; - count = ChronoUnit.HOURS.between(start, end); - if (count != 0) - return count > 1 ? count + " hours" : count + " hours"; - count = ChronoUnit.MINUTES.between(start, end); - if (count != 0) - return count > 1 ? count + " minutes" : count + " minute"; - count = ChronoUnit.SECONDS.between(start, end); - return count > 1 ? count + " seconds" : count + " second"; - } - - /** Singleton constructor. */ - private LangUtils() { - - } - -} diff --git a/org.argeo.util/src/org/argeo/util/OS.java b/org.argeo.util/src/org/argeo/util/OS.java deleted file mode 100644 index d8127b600..000000000 --- a/org.argeo.util/src/org/argeo/util/OS.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.argeo.util; - -import java.io.File; -import java.lang.management.ManagementFactory; - -/** When OS specific informations are needed. */ -public class OS { - public final static OS LOCAL = new OS(); - - private final String arch, name, version; - - /** The OS of the running JVM */ - protected OS() { - arch = System.getProperty("os.arch"); - name = System.getProperty("os.name"); - version = System.getProperty("os.version"); - } - - public String getArch() { - return arch; - } - - public String getName() { - return name; - } - - public String getVersion() { - return version; - } - - public boolean isMSWindows() { - // only MS Windows would use such an horrendous separator... - return File.separatorChar == '\\'; - } - - public String[] getDefaultShellCommand() { - if (!isMSWindows()) - return new String[] { "/bin/sh", "-l", "-i" }; - else - return new String[] { "cmd.exe", "/C" }; - } - - public static Integer getJvmPid() { - /* - * This method works on most platforms (including Linux). Although when Java 9 - * comes along, there is a better way: long pid = - * ProcessHandle.current().getPid(); - * - * See: - * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own- - * process-id - */ - String pidAndHost = ManagementFactory.getRuntimeMXBean().getName(); - return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@'))); - } -} diff --git a/org.argeo.util/src/org/argeo/util/PasswordEncryption.java b/org.argeo.util/src/org/argeo/util/PasswordEncryption.java deleted file mode 100644 index c95c7879e..000000000 --- a/org.argeo.util/src/org/argeo/util/PasswordEncryption.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.argeo.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -public class PasswordEncryption { - public final static Integer DEFAULT_ITERATION_COUNT = 1024; - /** Stronger with 256, but causes problem with Oracle JVM */ - public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256; - public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128; - public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; - public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; - public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; -// public final static String DEFAULT_CHARSET = "UTF-8"; - public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - - private Integer iterationCount = DEFAULT_ITERATION_COUNT; - private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH; - private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; - private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; - private String cipherName = DEFAULT_CIPHER_NAME; - - private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, - (byte) 0x35, (byte) 0xE3, (byte) 0x03 }; - private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, - (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, - (byte) 0x35, (byte) 0xE3, (byte) 0x03 }; - - private Key key; - private Cipher ecipher; - private Cipher dcipher; - - private String securityProviderName = null; - - /** - * This is up to the caller to clear the passed array. Neither copy of nor - * reference to the passed array is kept - */ - public PasswordEncryption(char[] password) { - this(password, DEFAULT_SALT_8, DEFAULT_IV_16); - } - - /** - * This is up to the caller to clear the passed array. Neither copies of nor - * references to the passed arrays are kept - */ - public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) { - try { - initKeyAndCiphers(password, passwordSalt, initializationVector); - } catch (InvalidKeyException e) { - Integer previousSecreteKeyLength = secreteKeyLength; - secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED; - System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength - + " secrete key length instead of " + previousSecreteKeyLength); - try { - initKeyAndCiphers(password, passwordSalt, initializationVector); - } catch (GeneralSecurityException e1) { - throw new IllegalStateException("Cannot get secret key (with restricted length)", e1); - } - } catch (GeneralSecurityException e) { - throw new IllegalStateException("Cannot get secret key", e); - } - } - - protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector) - throws GeneralSecurityException { - byte[] salt = new byte[8]; - System.arraycopy(passwordSalt, 0, salt, 0, salt.length); - // for (int i = 0; i < password.length && i < salt.length; i++) - // salt[i] = (byte) password[i]; - byte[] iv = new byte[16]; - System.arraycopy(initializationVector, 0, iv, 0, iv.length); - - SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName()); - PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength()); - String secKeyEncryption = getSecretKeyEncryption(); - if (secKeyEncryption != null) { - SecretKey tmp = keyFac.generateSecret(keySpec); - key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption()); - } else { - key = keyFac.generateSecret(keySpec); - } - if (securityProviderName != null) - ecipher = Cipher.getInstance(getCipherName(), securityProviderName); - else - ecipher = Cipher.getInstance(getCipherName()); - ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); - dcipher = Cipher.getInstance(getCipherName()); - dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); - } - - public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException { - try { - CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher); - StreamUtils.copy(decryptedIn, out); - StreamUtils.closeQuietly(out); - } catch (IOException e) { - throw e; - } finally { - StreamUtils.closeQuietly(decryptedIn); - } - } - - public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException { - try { - CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher); - StreamUtils.copy(decryptedIn, decryptedOut); - } catch (IOException e) { - throw e; - } finally { - StreamUtils.closeQuietly(encryptedIn); - } - } - - public byte[] encryptString(String str) { - ByteArrayOutputStream out = null; - ByteArrayInputStream in = null; - try { - out = new ByteArrayOutputStream(); - in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET)); - encrypt(in, out); - return out.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - StreamUtils.closeQuietly(out); - } - } - - /** Closes the input stream */ - public String decryptAsString(InputStream in) { - ByteArrayOutputStream out = null; - try { - out = new ByteArrayOutputStream(); - decrypt(in, out); - return new String(out.toByteArray(), DEFAULT_CHARSET); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - StreamUtils.closeQuietly(out); - } - } - - protected Key getKey() { - return key; - } - - protected Cipher getEcipher() { - return ecipher; - } - - protected Cipher getDcipher() { - return dcipher; - } - - protected Integer getIterationCount() { - return iterationCount; - } - - protected Integer getKeyLength() { - return secreteKeyLength; - } - - protected String getSecretKeyFactoryName() { - return secreteKeyFactoryName; - } - - protected String getSecretKeyEncryption() { - return secreteKeyEncryption; - } - - protected String getCipherName() { - return cipherName; - } - - public void setIterationCount(Integer iterationCount) { - this.iterationCount = iterationCount; - } - - public void setSecreteKeyLength(Integer keyLength) { - this.secreteKeyLength = keyLength; - } - - public void setSecreteKeyFactoryName(String secreteKeyFactoryName) { - this.secreteKeyFactoryName = secreteKeyFactoryName; - } - - public void setSecreteKeyEncryption(String secreteKeyEncryption) { - this.secreteKeyEncryption = secreteKeyEncryption; - } - - public void setCipherName(String cipherName) { - this.cipherName = cipherName; - } - - public void setSecurityProviderName(String securityProviderName) { - this.securityProviderName = securityProviderName; - } -} diff --git a/org.argeo.util/src/org/argeo/util/ServiceChannel.java b/org.argeo.util/src/org/argeo/util/ServiceChannel.java deleted file mode 100644 index 799738414..000000000 --- a/org.argeo.util/src/org/argeo/util/ServiceChannel.java +++ /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 read(ByteBuffer dst) { - return executor.submit(() -> in.read(dst)); - } - - @Override - public void read(ByteBuffer dst, A attachment, CompletionHandler handler) { - try { - Future res = read(dst); - handler.completed(res.get(), attachment); - } catch (Exception e) { - handler.failed(e, attachment); - } - } - - @Override - public Future write(ByteBuffer src) { - return executor.submit(() -> out.write(src)); - } - - @Override - public void write(ByteBuffer src, A attachment, CompletionHandler handler) { - try { - Future res = write(src); - handler.completed(res.get(), attachment); - } catch (Exception e) { - handler.failed(e, attachment); - } - } - - @Override - public synchronized void close() throws IOException { - try { - in.close(); - } catch (Exception e) { - e.printStackTrace(); - } - try { - out.close(); - } catch (Exception e) { - e.printStackTrace(); - } - open = false; - notifyAll(); - } - - @Override - public synchronized boolean isOpen() { - return open; - } - -} diff --git a/org.argeo.util/src/org/argeo/util/StreamUtils.java b/org.argeo.util/src/org/argeo/util/StreamUtils.java deleted file mode 100644 index 6d7d940ce..000000000 --- a/org.argeo.util/src/org/argeo/util/StreamUtils.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.argeo.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Writer; - -/** Utilities to be used when Apache Commons IO is not available. */ -class StreamUtils { - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - - /* - * APACHE COMMONS IO (inspired) - */ - - /** @return the number of bytes */ - public static Long copy(InputStream in, OutputStream out) - throws IOException { - Long count = 0l; - byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; - while (true) { - int length = in.read(buf); - if (length < 0) - break; - out.write(buf, 0, length); - count = count + length; - } - return count; - } - - /** @return the number of chars */ - public static Long copy(Reader in, Writer out) throws IOException { - Long count = 0l; - char[] buf = new char[DEFAULT_BUFFER_SIZE]; - while (true) { - int length = in.read(buf); - if (length < 0) - break; - out.write(buf, 0, length); - count = count + length; - } - return count; - } - - public static void closeQuietly(InputStream in) { - if (in != null) - try { - in.close(); - } catch (Exception e) { - // - } - } - - public static void closeQuietly(OutputStream out) { - if (out != null) - try { - out.close(); - } catch (Exception e) { - // - } - } - - public static void closeQuietly(Reader in) { - if (in != null) - try { - in.close(); - } catch (Exception e) { - // - } - } - - public static void closeQuietly(Writer out) { - if (out != null) - try { - out.close(); - } catch (Exception e) { - // - } - } -} diff --git a/org.argeo.util/src/org/argeo/util/Tester.java b/org.argeo.util/src/org/argeo/util/Tester.java deleted file mode 100644 index 31a2be4ec..000000000 --- a/org.argeo.util/src/org/argeo/util/Tester.java +++ /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 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 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 findMethods(Class clss) { - List methods = new ArrayList(); -// 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 r = test.results; - for (String uid : r.keySet()) { - TesterStatus testStatus = r.get(uid); - System.out.println(testStatus); - } - } - - public static String usage() { - return "java " + Tester.class.getName() + " [test class name]"; - - } -} diff --git a/org.argeo.util/src/org/argeo/util/TesterStatus.java b/org.argeo.util/src/org/argeo/util/TesterStatus.java deleted file mode 100644 index d1d14ed06..000000000 --- a/org.argeo.util/src/org/argeo/util/TesterStatus.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.util; - -import java.io.Serializable; - -/** The status of a test. */ -public class TesterStatus implements Serializable { - private static final long serialVersionUID = 6272975746885487000L; - - private Boolean passed = null; - private final String uid; - private Throwable throwable = null; - - public TesterStatus(String uid) { - this.uid = uid; - } - - /** For cloning. */ - public TesterStatus(String uid, Boolean passed, Throwable throwable) { - this(uid); - this.passed = passed; - this.throwable = throwable; - } - - public synchronized Boolean isRunning() { - return passed == null; - } - - public synchronized Boolean isPassed() { - assert passed != null; - return passed; - } - - public synchronized Boolean isFailed() { - assert passed != null; - return !passed; - } - - public synchronized void setPassed() { - setStatus(true); - } - - public synchronized void setFailed() { - setStatus(false); - } - - public synchronized void setFailed(Throwable throwable) { - setStatus(false); - setThrowable(throwable); - } - - protected void setStatus(Boolean passed) { - if (this.passed != null) - throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")"); - this.passed = passed; - } - - protected void setThrowable(Throwable throwable) { - if (this.throwable != null) - throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")"); - this.throwable = throwable; - } - - public String getUid() { - return uid; - } - - public Throwable getThrowable() { - return throwable; - } - - @Override - protected Object clone() throws CloneNotSupportedException { - // TODO Auto-generated method stub - return super.clone(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof TesterStatus) { - TesterStatus other = (TesterStatus) o; - // we don't check consistency for performance purposes - // this equals() is supposed to be used in collections or for transfer - return other.uid.equals(uid); - } - return false; - } - - @Override - public int hashCode() { - return uid.hashCode(); - } - - @Override - public String toString() { - return uid + "\t" + (passed ? "passed" : "failed"); - } - -} diff --git a/org.argeo.util/src/org/argeo/util/Throughput.java b/org.argeo.util/src/org/argeo/util/Throughput.java deleted file mode 100644 index 266ddbc58..000000000 --- a/org.argeo.util/src/org/argeo/util/Throughput.java +++ /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 /, e.g. 3.54/s or 1500/h"); - String valueStr = def.substring(0, index); - String unitStr = def.substring(index + 1); - try { - this.value = usNumberFormat.parse(valueStr).doubleValue(); - } catch (ParseException e) { - throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e); - } - this.unit = Unit.valueOf(unitStr); - } - - public Long asMsPeriod() { - if (unit.equals(Unit.s)) - return Math.round(1000d / value); - else if (unit.equals(Unit.m)) - return Math.round((60d * 1000d) / value); - else if (unit.equals(Unit.h)) - return Math.round((60d * 60d * 1000d) / value); - else if (unit.equals(Unit.d)) - return Math.round((24d * 60d * 60d * 1000d) / value); - else - throw new IllegalArgumentException("Unsupported unit " + unit); - } - - @Override - public String toString() { - return usNumberFormat.format(value) + '/' + unit; - } - - public Double getValue() { - return value; - } - - public Unit getUnit() { - return unit; - } - -} diff --git a/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java b/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java deleted file mode 100644 index 7c645f3a3..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.argeo.util.naming; - -import java.util.Dictionary; -import java.util.Enumeration; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; - -public class AttributesDictionary extends Dictionary { - 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 keys() { - NamingEnumeration namingEnumeration = attributes.getIDs(); - return new Enumeration() { - - @Override - public boolean hasMoreElements() { - return namingEnumeration.hasMoreElements(); - } - - @Override - public String nextElement() { - return namingEnumeration.nextElement(); - } - - }; - } - - @Override - public Enumeration elements() { - NamingEnumeration namingEnumeration = attributes.getIDs(); - return new Enumeration() { - - @Override - public boolean hasMoreElements() { - return namingEnumeration.hasMoreElements(); - } - - @Override - public Object nextElement() { - String key = namingEnumeration.nextElement(); - return get(key); - } - - }; - } - - @Override - /** @returns a String or String[] */ - 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 content of an {@link Attributes} to the provided - * {@link Dictionary}. - */ - public static void copy(Attributes attributes, Dictionary dictionary) { - AttributesDictionary ad = new AttributesDictionary(attributes); - Enumeration 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 dictionary, Attributes attributes) { - AttributesDictionary ad = new AttributesDictionary(attributes); - Enumeration keys = dictionary.keys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - ad.put(key, dictionary.get(key)); - } - } -} diff --git a/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java b/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java deleted file mode 100644 index 973b90f0f..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.argeo.util.naming; - -import java.io.IOException; -import java.util.Arrays; -import java.util.StringTokenizer; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.argeo.osgi.useradmin.UserDirectoryException; - -/** LDAP authPassword field according to RFC 3112 */ -public class AuthPassword implements CallbackHandler { - private final String authScheme; - private final String authInfo; - private final String authValue; - - public AuthPassword(String value) { - StringTokenizer st = new StringTokenizer(value, "$"); - // TODO make it more robust, deal with bad formatting - this.authScheme = st.nextToken().trim(); - this.authInfo = st.nextToken().trim(); - this.authValue = st.nextToken().trim(); - - String expectedAuthScheme = getExpectedAuthScheme(); - if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme)) - throw new IllegalArgumentException( - "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme); - } - - protected AuthPassword(String authInfo, String authValue) { - this.authScheme = getExpectedAuthScheme(); - if (authScheme == null) - throw new IllegalArgumentException("Expected auth scheme cannot be null"); - this.authInfo = authInfo; - this.authValue = authValue; - } - - protected AuthPassword(AuthPassword authPassword) { - this.authScheme = authPassword.getAuthScheme(); - this.authInfo = authPassword.getAuthInfo(); - this.authValue = authPassword.getAuthValue(); - } - - protected String getExpectedAuthScheme() { - return null; - } - - protected boolean matchAuthValue(Object object) { - return authValue.equals(object.toString()); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof AuthPassword)) - return false; - AuthPassword authPassword = (AuthPassword) obj; - return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo) - && authValue.equals(authValue); - } - - public boolean keyEquals(AuthPassword authPassword) { - return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo); - } - - @Override - public int hashCode() { - return authValue.hashCode(); - } - - @Override - public String toString() { - return toAuthPassword(); - } - - public final String toAuthPassword() { - return getAuthScheme() + '$' + authInfo + '$' + authValue; - } - - public String getAuthScheme() { - return authScheme; - } - - public String getAuthInfo() { - return authInfo; - } - - public String getAuthValue() { - return authValue; - } - - public static AuthPassword matchAuthValue(Attributes attributes, char[] value) { - try { - Attribute authPassword = attributes.get(LdapAttrs.authPassword.name()); - if (authPassword != null) { - NamingEnumeration values = authPassword.getAll(); - while (values.hasMore()) { - Object val = values.next(); - AuthPassword token = new AuthPassword(val.toString()); - String auth; - if (Arrays.binarySearch(value, '$') >= 0) { - auth = token.authInfo + '$' + token.authValue; - } else { - auth = token.authValue; - } - if (Arrays.equals(auth.toCharArray(), value)) - return token; - // if (token.matchAuthValue(value)) - // return token; - } - } - return null; - } catch (NamingException e) { - throw new UserDirectoryException("Cannot check attribute", e); - } - } - - public static boolean remove(Attributes attributes, AuthPassword value) { - Attribute authPassword = attributes.get(LdapAttrs.authPassword.name()); - return authPassword.remove(value.toAuthPassword()); - } - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) - ((NameCallback) callback).setName(toAuthPassword()); - else if (callback instanceof PasswordCallback) - ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray()); - } - } - -} diff --git a/org.argeo.util/src/org/argeo/util/naming/Distinguished.java b/org.argeo.util/src/org/argeo/util/naming/Distinguished.java deleted file mode 100644 index 6aefc1617..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/Distinguished.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.util.naming; - -import java.util.EnumSet; -import java.util.Set; -import java.util.TreeSet; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -/** - * An object that can be identified with an X.500 distinguished name. - * - * @see https://tools.ietf.org/html/rfc1779 - */ -public interface Distinguished { - /** The related distinguished name. */ - String dn(); - - /** The related distinguished name as an {@link LdapName}. */ - default LdapName distinguishedName() { - try { - return new LdapName(dn()); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e); - } - } - - /** List all DNs of an enumeration as strings. */ - static Set enumToDns(EnumSet enumSet) { - Set res = new TreeSet<>(); - for (Enum enm : enumSet) { - res.add(((Distinguished) enm).dn()); - } - return res; - } -} diff --git a/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java b/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java deleted file mode 100644 index 1a67eea36..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.argeo.util.naming; - -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collections; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; - -import javax.naming.Binding; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; - -public class DnsBrowser implements Closeable { - private final DirContext initialCtx; - - public DnsBrowser() throws NamingException { - this(null); - } - - public DnsBrowser(String dnsServerUrls) throws NamingException { - Hashtable 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> getAllRecords(String name) throws NamingException { - Map> res = new TreeMap<>(); - Attributes attrs = initialCtx.getAttributes(name); - NamingEnumeration ids = attrs.getIDs(); - while (ids.hasMore()) { - String recordType = ids.next(); - List lst = new ArrayList(); - 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 getRecords(String name, String recordType) throws NamingException { - List res = new ArrayList(); - Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType }); - Attribute attr = attrs.get(recordType); - addValues(attr, res); - return res; - } - - /** Ordered, with preferred first. */ - public List getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException { - List raw = getRecords(name, "SRV"); - if (raw.size() == 0) - return null; - SortedSet 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 lst = new ArrayList<>(); - for (SrvRecord order : res) { - lst.add(order.toHost(withPort)); - } - return Collections.unmodifiableList(lst); - } - - private void addValues(Attribute attr, List 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 listEntries(String name) throws NamingException { - List res = new ArrayList(); - NamingEnumeration 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> 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 [ | *]"); - } - -} \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv b/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv deleted file mode 100644 index 676d72720..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv +++ /dev/null @@ -1,129 +0,0 @@ -uid,,,0.9.2342.19200300.100.1.1,,RFC 4519 -mail,,,0.9.2342.19200300.100.1.3,,RFC 4524 -info,,,0.9.2342.19200300.100.1.4,,RFC 4524 -drink,,,0.9.2342.19200300.100.1.5,,RFC 4524 -roomNumber,,,0.9.2342.19200300.100.1.6,,RFC 4524 -photo,,,0.9.2342.19200300.100.1.7,,RFC 2798 -userClass,,,0.9.2342.19200300.100.1.8,,RFC 4524 -host,,,0.9.2342.19200300.100.1.9,,RFC 4524 -manager,,,0.9.2342.19200300.100.1.10,,RFC 4524 -documentIdentifier,,,0.9.2342.19200300.100.1.11,,RFC 4524 -documentTitle,,,0.9.2342.19200300.100.1.12,,RFC 4524 -documentVersion,,,0.9.2342.19200300.100.1.13,,RFC 4524 -documentAuthor,,,0.9.2342.19200300.100.1.14,,RFC 4524 -documentLocation,,,0.9.2342.19200300.100.1.15,,RFC 4524 -homePhone,,,0.9.2342.19200300.100.1.20,,RFC 4524 -secretary,,,0.9.2342.19200300.100.1.21,,RFC 4524 -dc,,,0.9.2342.19200300.100.1.25,,RFC 4519 -associatedDomain,,,0.9.2342.19200300.100.1.37,,RFC 4524 -associatedName,,,0.9.2342.19200300.100.1.38,,RFC 4524 -homePostalAddress,,,0.9.2342.19200300.100.1.39,,RFC 4524 -personalTitle,,,0.9.2342.19200300.100.1.40,,RFC 4524 -mobile,,,0.9.2342.19200300.100.1.41,,RFC 4524 -pager,,,0.9.2342.19200300.100.1.42,,RFC 4524 -co,,,0.9.2342.19200300.100.1.43,,RFC 4524 -uniqueIdentifier,,,0.9.2342.19200300.100.1.44,,RFC 4524 -organizationalStatus,,,0.9.2342.19200300.100.1.45,,RFC 4524 -buildingName,,,0.9.2342.19200300.100.1.48,,RFC 4524 -audio,,,0.9.2342.19200300.100.1.55,,RFC 2798 -documentPublisher,,,0.9.2342.19200300.100.1.56,,RFC 4524 -jpegPhoto,,,0.9.2342.19200300.100.1.60,,RFC 2798 -vendorName,,,1.3.6.1.1.4,,RFC 3045 -vendorVersion,,,1.3.6.1.1.5,,RFC 3045 -entryUUID,,,1.3.6.1.1.16.4,,RFC 4530 -entryDN,,,1.3.6.1.1.20,,RFC 5020 -labeledURI,,,1.3.6.1.4.1.250.1.57,,RFC 2798 -numSubordinates,,,1.3.6.1.4.1.453.16.2.103,,draft-ietf-boreham-numsubordinates -namingContexts,,,1.3.6.1.4.1.1466.101.120.5,,RFC 4512 -altServer,,,1.3.6.1.4.1.1466.101.120.6,,RFC 4512 -supportedExtension,,,1.3.6.1.4.1.1466.101.120.7,,RFC 4512 -supportedControl,,,1.3.6.1.4.1.1466.101.120.13,,RFC 4512 -supportedSASLMechanisms,,,1.3.6.1.4.1.1466.101.120.14,,RFC 4512 -supportedLDAPVersion,,,1.3.6.1.4.1.1466.101.120.15,,RFC 4512 -ldapSyntaxes,,,1.3.6.1.4.1.1466.101.120.16,,RFC 4512 -supportedAuthPasswordSchemes,,,1.3.6.1.4.1.4203.1.3.3,,RFC 3112 -authPassword,,,1.3.6.1.4.1.4203.1.3.4,,RFC 3112 -supportedFeatures,,,1.3.6.1.4.1.4203.1.3.5,,RFC 4512 -inheritable,,,1.3.6.1.4.1.7628.5.4.1,,draft-ietf-ldup-subentry -blockInheritance,,,1.3.6.1.4.1.7628.5.4.2,,draft-ietf-ldup-subentry -objectClass,,,2.5.4.0,,RFC 4512 -aliasedObjectName,,,2.5.4.1,,RFC 4512 -cn,,,2.5.4.3,,RFC 4519 -sn,,,2.5.4.4,,RFC 4519 -serialNumber,,,2.5.4.5,,RFC 4519 -c,,,2.5.4.6,,RFC 4519 -l,,,2.5.4.7,,RFC 4519 -st,,,2.5.4.8,,RFC 4519 -street,,,2.5.4.9,,RFC 4519 -o,,,2.5.4.10,,RFC 4519 -ou,,,2.5.4.11,,RFC 4519 -title,,,2.5.4.12,,RFC 4519 -description,,,2.5.4.13,,RFC 4519 -searchGuide,,,2.5.4.14,,RFC 4519 -businessCategory,,,2.5.4.15,,RFC 4519 -postalAddress,,,2.5.4.16,,RFC 4519 -postalCode,,,2.5.4.17,,RFC 4519 -postOfficeBox,,,2.5.4.18,,RFC 4519 -physicalDeliveryOfficeName,,,2.5.4.19,,RFC 4519 -telephoneNumber,,,2.5.4.20,,RFC 4519 -telexNumber,,,2.5.4.21,,RFC 4519 -teletexTerminalIdentifier,,,2.5.4.22,,RFC 4519 -facsimileTelephoneNumber,,,2.5.4.23,,RFC 4519 -x121Address,,,2.5.4.24,,RFC 4519 -internationalISDNNumber,,,2.5.4.25,,RFC 4519 -registeredAddress,,,2.5.4.26,,RFC 4519 -destinationIndicator,,,2.5.4.27,,RFC 4519 -preferredDeliveryMethod,,,2.5.4.28,,RFC 4519 -member,,,2.5.4.31,,RFC 4519 -owner,,,2.5.4.32,,RFC 4519 -roleOccupant,,,2.5.4.33,,RFC 4519 -seeAlso,,,2.5.4.34,,RFC 4519 -userPassword,,,2.5.4.35,,RFC 4519 -userCertificate,,,2.5.4.36,,RFC 4523 -cACertificate,,,2.5.4.37,,RFC 4523 -authorityRevocationList,,,2.5.4.38,,RFC 4523 -certificateRevocationList,,,2.5.4.39,,RFC 4523 -crossCertificatePair,,,2.5.4.40,,RFC 4523 -name,,,2.5.4.41,,RFC 4519 -givenName,,,2.5.4.42,,RFC 4519 -initials,,,2.5.4.43,,RFC 4519 -generationQualifier,,,2.5.4.44,,RFC 4519 -x500UniqueIdentifier,,,2.5.4.45,,RFC 4519 -dnQualifier,,,2.5.4.46,,RFC 4519 -enhancedSearchGuide,,,2.5.4.47,,RFC 4519 -distinguishedName,,,2.5.4.49,,RFC 4519 -uniqueMember,,,2.5.4.50,,RFC 4519 -houseIdentifier,,,2.5.4.51,,RFC 4519 -supportedAlgorithms,,,2.5.4.52,,RFC 4523 -deltaRevocationList,,,2.5.4.53,,RFC 4523 -createTimestamp,,,2.5.18.1,,RFC 4512 -modifyTimestamp,,,2.5.18.2,,RFC 4512 -creatorsName,,,2.5.18.3,,RFC 4512 -modifiersName,,,2.5.18.4,,RFC 4512 -subschemaSubentry,,,2.5.18.10,,RFC 4512 -dITStructureRules,,,2.5.21.1,,RFC 4512 -dITContentRules,,,2.5.21.2,,RFC 4512 -matchingRules,,,2.5.21.4,,RFC 4512 -attributeTypes,,,2.5.21.5,,RFC 4512 -objectClasses,,,2.5.21.6,,RFC 4512 -nameForms,,,2.5.21.7,,RFC 4512 -matchingRuleUse,,,2.5.21.8,,RFC 4512 -structuralObjectClass,,,2.5.21.9,,RFC 4512 -governingStructureRule,,,2.5.21.10,,RFC 4512 -carLicense,,,2.16.840.1.113730.3.1.1,,RFC 2798 -departmentNumber,,,2.16.840.1.113730.3.1.2,,RFC 2798 -employeeNumber,,,2.16.840.1.113730.3.1.3,,RFC 2798 -employeeType,,,2.16.840.1.113730.3.1.4,,RFC 2798 -changeNumber,,,2.16.840.1.113730.3.1.5,,draft-good-ldap-changelog -targetDN,,,2.16.840.1.113730.3.1.6,,draft-good-ldap-changelog -changeType,,,2.16.840.1.113730.3.1.7,,draft-good-ldap-changelog -changes,,,2.16.840.1.113730.3.1.8,,draft-good-ldap-changelog -newRDN,,,2.16.840.1.113730.3.1.9,,draft-good-ldap-changelog -deleteOldRDN,,,2.16.840.1.113730.3.1.10,,draft-good-ldap-changelog -newSuperior,,,2.16.840.1.113730.3.1.11,,draft-good-ldap-changelog -ref,,,2.16.840.1.113730.3.1.34,,RFC 3296 -changelog,,,2.16.840.1.113730.3.1.35,,draft-good-ldap-changelog -preferredLanguage,,,2.16.840.1.113730.3.1.39,,RFC 2798 -userSMIMECertificate,,,2.16.840.1.113730.3.1.40,,RFC 2798 -userPKCS12,,,2.16.840.1.113730.3.1.216,,RFC 2798 -displayName,,,2.16.840.1.113730.3.1.241,,RFC 2798 diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java b/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java deleted file mode 100644 index 43cfe03b4..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java +++ /dev/null @@ -1,349 +0,0 @@ -package org.argeo.util.naming; - -import java.util.function.Supplier; - -/** - * Standard LDAP attributes as per:
    - * - Standard LDAP
    - * - Kerberos - * LDAP (partial) - */ -public enum LdapAttrs implements SpecifiedName, Supplier { - /** */ - uid("0.9.2342.19200300.100.1.1", "RFC 4519"), - /** */ - mail("0.9.2342.19200300.100.1.3", "RFC 4524"), - /** */ - info("0.9.2342.19200300.100.1.4", "RFC 4524"), - /** */ - drink("0.9.2342.19200300.100.1.5", "RFC 4524"), - /** */ - roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"), - /** */ - photo("0.9.2342.19200300.100.1.7", "RFC 2798"), - /** */ - userClass("0.9.2342.19200300.100.1.8", "RFC 4524"), - /** */ - host("0.9.2342.19200300.100.1.9", "RFC 4524"), - /** */ - manager("0.9.2342.19200300.100.1.10", "RFC 4524"), - /** */ - documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"), - /** */ - documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"), - /** */ - documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"), - /** */ - documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"), - /** */ - documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"), - /** */ - homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"), - /** */ - secretary("0.9.2342.19200300.100.1.21", "RFC 4524"), - /** */ - dc("0.9.2342.19200300.100.1.25", "RFC 4519"), - /** */ - associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"), - /** */ - associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"), - /** */ - homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"), - /** */ - personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"), - /** */ - mobile("0.9.2342.19200300.100.1.41", "RFC 4524"), - /** */ - pager("0.9.2342.19200300.100.1.42", "RFC 4524"), - /** */ - co("0.9.2342.19200300.100.1.43", "RFC 4524"), - /** */ - uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"), - /** */ - organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"), - /** */ - buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"), - /** */ - audio("0.9.2342.19200300.100.1.55", "RFC 2798"), - /** */ - documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"), - /** */ - jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"), - /** */ - vendorName("1.3.6.1.1.4", "RFC 3045"), - /** */ - vendorVersion("1.3.6.1.1.5", "RFC 3045"), - /** */ - entryUUID("1.3.6.1.1.16.4", "RFC 4530"), - /** */ - entryDN("1.3.6.1.1.20", "RFC 5020"), - /** */ - labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"), - /** */ - numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"), - /** */ - namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"), - /** */ - altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"), - /** */ - supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"), - /** */ - supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"), - /** */ - supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"), - /** */ - supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"), - /** */ - ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"), - /** */ - supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"), - /** */ - authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"), - /** */ - supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"), - /** */ - inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"), - /** */ - blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"), - /** */ - objectClass("2.5.4.0", "RFC 4512"), - /** */ - aliasedObjectName("2.5.4.1", "RFC 4512"), - /** */ - cn("2.5.4.3", "RFC 4519"), - /** */ - sn("2.5.4.4", "RFC 4519"), - /** */ - serialNumber("2.5.4.5", "RFC 4519"), - /** */ - c("2.5.4.6", "RFC 4519"), - /** */ - l("2.5.4.7", "RFC 4519"), - /** */ - st("2.5.4.8", "RFC 4519"), - /** */ - street("2.5.4.9", "RFC 4519"), - /** */ - o("2.5.4.10", "RFC 4519"), - /** */ - ou("2.5.4.11", "RFC 4519"), - /** */ - title("2.5.4.12", "RFC 4519"), - /** */ - description("2.5.4.13", "RFC 4519"), - /** */ - searchGuide("2.5.4.14", "RFC 4519"), - /** */ - businessCategory("2.5.4.15", "RFC 4519"), - /** */ - postalAddress("2.5.4.16", "RFC 4519"), - /** */ - postalCode("2.5.4.17", "RFC 4519"), - /** */ - postOfficeBox("2.5.4.18", "RFC 4519"), - /** */ - physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"), - /** */ - telephoneNumber("2.5.4.20", "RFC 4519"), - /** */ - telexNumber("2.5.4.21", "RFC 4519"), - /** */ - teletexTerminalIdentifier("2.5.4.22", "RFC 4519"), - /** */ - facsimileTelephoneNumber("2.5.4.23", "RFC 4519"), - /** */ - x121Address("2.5.4.24", "RFC 4519"), - /** */ - internationalISDNNumber("2.5.4.25", "RFC 4519"), - /** */ - registeredAddress("2.5.4.26", "RFC 4519"), - /** */ - destinationIndicator("2.5.4.27", "RFC 4519"), - /** */ - preferredDeliveryMethod("2.5.4.28", "RFC 4519"), - /** */ - member("2.5.4.31", "RFC 4519"), - /** */ - owner("2.5.4.32", "RFC 4519"), - /** */ - roleOccupant("2.5.4.33", "RFC 4519"), - /** */ - seeAlso("2.5.4.34", "RFC 4519"), - /** */ - userPassword("2.5.4.35", "RFC 4519"), - /** */ - userCertificate("2.5.4.36", "RFC 4523"), - /** */ - cACertificate("2.5.4.37", "RFC 4523"), - /** */ - authorityRevocationList("2.5.4.38", "RFC 4523"), - /** */ - certificateRevocationList("2.5.4.39", "RFC 4523"), - /** */ - crossCertificatePair("2.5.4.40", "RFC 4523"), - /** */ - name("2.5.4.41", "RFC 4519"), - /** */ - givenName("2.5.4.42", "RFC 4519"), - /** */ - initials("2.5.4.43", "RFC 4519"), - /** */ - generationQualifier("2.5.4.44", "RFC 4519"), - /** */ - x500UniqueIdentifier("2.5.4.45", "RFC 4519"), - /** */ - dnQualifier("2.5.4.46", "RFC 4519"), - /** */ - enhancedSearchGuide("2.5.4.47", "RFC 4519"), - /** */ - distinguishedName("2.5.4.49", "RFC 4519"), - /** */ - uniqueMember("2.5.4.50", "RFC 4519"), - /** */ - houseIdentifier("2.5.4.51", "RFC 4519"), - /** */ - supportedAlgorithms("2.5.4.52", "RFC 4523"), - /** */ - deltaRevocationList("2.5.4.53", "RFC 4523"), - /** */ - createTimestamp("2.5.18.1", "RFC 4512"), - /** */ - modifyTimestamp("2.5.18.2", "RFC 4512"), - /** */ - creatorsName("2.5.18.3", "RFC 4512"), - /** */ - modifiersName("2.5.18.4", "RFC 4512"), - /** */ - subschemaSubentry("2.5.18.10", "RFC 4512"), - /** */ - dITStructureRules("2.5.21.1", "RFC 4512"), - /** */ - dITContentRules("2.5.21.2", "RFC 4512"), - /** */ - matchingRules("2.5.21.4", "RFC 4512"), - /** */ - attributeTypes("2.5.21.5", "RFC 4512"), - /** */ - objectClasses("2.5.21.6", "RFC 4512"), - /** */ - nameForms("2.5.21.7", "RFC 4512"), - /** */ - matchingRuleUse("2.5.21.8", "RFC 4512"), - /** */ - structuralObjectClass("2.5.21.9", "RFC 4512"), - /** */ - governingStructureRule("2.5.21.10", "RFC 4512"), - /** */ - carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"), - /** */ - departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"), - /** */ - employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"), - /** */ - employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"), - /** */ - changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"), - /** */ - targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"), - /** */ - changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"), - /** */ - changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"), - /** */ - newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"), - /** */ - deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"), - /** */ - newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"), - /** */ - ref("2.16.840.1.113730.3.1.34", "RFC 3296"), - /** */ - changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"), - /** */ - preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"), - /** */ - userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"), - /** */ - userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"), - /** */ - displayName("2.16.840.1.113730.3.1.241", "RFC 2798"), - - // Sun memberOf - memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"), - - // KERBEROS (partial) - krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"), - - // RFC 2985 and RFC 3039 (partial) - dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"), - /** */ - placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"), - /** */ - gender("1.3.6.1.5.5.7.9.3", "RFC 2985"), - /** */ - countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"), - /** */ - countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"), - // - ; - - public final static String DN = "dn"; - -// private final static String LDAP_ = "ldap:"; - - private final String oid, spec; - - LdapAttrs(String oid, String spec) { - this.oid = oid; - this.spec = spec; - } - - @Override - public String getID() { - return oid; - } - - @Override - public String getSpec() { - return spec; - } - - public String getPrefix() { - return prefix(); - } - - public static String prefix() { - return "ldap"; - } - - @Deprecated - public String property() { - return get(); - } - - @Deprecated - public String qualified() { - return get(); - } - - public String get() { - String prefix = getPrefix(); - return prefix != null ? prefix + ":" + name() : name(); - } - - public String getNamespace() { - return namespace(); - } - - public static String namespace() { - return "http://www.argeo.org/ns/ldap"; - } - - @Override - public final String toString() { - // must return the name - return name(); - } - -} diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv b/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv deleted file mode 100644 index 3d907cbeb..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv +++ /dev/null @@ -1,42 +0,0 @@ -account,,,0.9.2342.19200300.100.4.5,,RFC 4524 -document,,,0.9.2342.19200300.100.4.6,,RFC 4524 -room,,,0.9.2342.19200300.100.4.7,,RFC 4524 -documentSeries,,,0.9.2342.19200300.100.4.9,,RFC 4524 -domain,,,0.9.2342.19200300.100.4.13,,RFC 4524 -rFC822localPart,,,0.9.2342.19200300.100.4.14,,RFC 4524 -domainRelatedObject,,,0.9.2342.19200300.100.4.17,,RFC 4524 -friendlyCountry,,,0.9.2342.19200300.100.4.18,,RFC 4524 -simpleSecurityObject,,,0.9.2342.19200300.100.4.19,,RFC 4524 -uidObject,,,1.3.6.1.1.3.1,,RFC 4519 -extensibleObject,,,1.3.6.1.4.1.1466.101.120.111,,RFC 4512 -dcObject,,,1.3.6.1.4.1.1466.344,,RFC 4519 -authPasswordObject,,,1.3.6.1.4.1.4203.1.4.7,,RFC 3112 -namedObject,,,1.3.6.1.4.1.5322.13.1.1,,draft-howard-namedobject -inheritableLDAPSubEntry,,,1.3.6.1.4.1.7628.5.6.1.1,,draft-ietf-ldup-subentry -top,,,2.5.6.0,,RFC 4512 -alias,,,2.5.6.1,,RFC 4512 -country,,,2.5.6.2,,RFC 4519 -locality,,,2.5.6.3,,RFC 4519 -organization,,,2.5.6.4,,RFC 4519 -organizationalUnit,,,2.5.6.5,,RFC 4519 -person,,,2.5.6.6,,RFC 4519 -organizationalPerson,,,2.5.6.7,,RFC 4519 -organizationalRole,,,2.5.6.8,,RFC 4519 -groupOfNames,,,2.5.6.9,,RFC 4519 -residentialPerson,,,2.5.6.10,,RFC 4519 -applicationProcess,,,2.5.6.11,,RFC 4519 -device,,,2.5.6.14,,RFC 4519 -strongAuthenticationUser,,,2.5.6.15,,RFC 4523 -certificationAuthority,,,2.5.6.16,,RFC 4523 -certificationAuthority-V2,,,2.5.6.16.2,,RFC 4523 -groupOfUniqueNames,,,2.5.6.17,,RFC 4519 -userSecurityInformation,,,2.5.6.18,,RFC 4523 -cRLDistributionPoint,,,2.5.6.19,,RFC 4523 -pkiUser,,,2.5.6.21,,RFC 4523 -pkiCA,,,2.5.6.22,,RFC 4523 -deltaCRL,,,2.5.6.23,,RFC 4523 -subschema,,,2.5.20.1,,RFC 4512 -ldapSubEntry,,,2.16.840.1.113719.2.142.6.1.1,,draft-ietf-ldup-subentry -changeLogEntry,,,2.16.840.1.113730.3.2.1,,draft-good-ldap-changelog -inetOrgPerson,,,2.16.840.1.113730.3.2.2,,RFC 2798 -referral,,,2.16.840.1.113730.3.2.6,,RFC 3296 diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java b/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java deleted file mode 100644 index c616d1491..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.argeo.util.naming; - -/** - * Standard LDAP object classes as per - * https://www.ldap.com/ldap- - * oid-reference - */ -public enum LdapObjs implements SpecifiedName { - account("0.9.2342.19200300.100.4.5", "RFC 4524"), - /** */ - document("0.9.2342.19200300.100.4.6", "RFC 4524"), - /** */ - room("0.9.2342.19200300.100.4.7", "RFC 4524"), - /** */ - documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"), - /** */ - domain("0.9.2342.19200300.100.4.13", "RFC 4524"), - /** */ - rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"), - /** */ - domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"), - /** */ - friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"), - /** */ - simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"), - /** */ - uidObject("1.3.6.1.1.3.1", "RFC 4519"), - /** */ - extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"), - /** */ - dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"), - /** */ - authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"), - /** */ - namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"), - /** */ - inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"), - /** */ - top("2.5.6.0", "RFC 4512"), - /** */ - alias("2.5.6.1", "RFC 4512"), - /** */ - country("2.5.6.2", "RFC 4519"), - /** */ - locality("2.5.6.3", "RFC 4519"), - /** */ - organization("2.5.6.4", "RFC 4519"), - /** */ - organizationalUnit("2.5.6.5", "RFC 4519"), - /** */ - person("2.5.6.6", "RFC 4519"), - /** */ - organizationalPerson("2.5.6.7", "RFC 4519"), - /** */ - organizationalRole("2.5.6.8", "RFC 4519"), - /** */ - groupOfNames("2.5.6.9", "RFC 4519"), - /** */ - residentialPerson("2.5.6.10", "RFC 4519"), - /** */ - applicationProcess("2.5.6.11", "RFC 4519"), - /** */ - device("2.5.6.14", "RFC 4519"), - /** */ - strongAuthenticationUser("2.5.6.15", "RFC 4523"), - /** */ - certificationAuthority("2.5.6.16", "RFC 4523"), - // /** Should be certificationAuthority-V2 */ - // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") { - // }, - /** */ - groupOfUniqueNames("2.5.6.17", "RFC 4519"), - /** */ - userSecurityInformation("2.5.6.18", "RFC 4523"), - /** */ - cRLDistributionPoint("2.5.6.19", "RFC 4523"), - /** */ - pkiUser("2.5.6.21", "RFC 4523"), - /** */ - pkiCA("2.5.6.22", "RFC 4523"), - /** */ - deltaCRL("2.5.6.23", "RFC 4523"), - /** */ - subschema("2.5.20.1", "RFC 4512"), - /** */ - ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"), - /** */ - changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"), - /** */ - inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"), - /** */ - referral("2.16.840.1.113730.3.2.6", "RFC 3296"); - - private final static String LDAP_ = "ldap:"; - private final String oid, spec; - - private LdapObjs(String oid, String spec) { - this.oid = oid; - this.spec = spec; - } - - public String getOid() { - return oid; - } - - public String getSpec() { - return spec; - } - - public String property() { - return new StringBuilder(LDAP_).append(name()).toString(); - } - -} diff --git a/org.argeo.util/src/org/argeo/util/naming/LdifParser.java b/org.argeo.util/src/org/argeo/util/naming/LdifParser.java deleted file mode 100644 index d68173a2a..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdifParser.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.argeo.util.naming; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.naming.InvalidNameException; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.osgi.useradmin.UserDirectoryException; - -/** Basic LDIF parser. */ -public class LdifParser { - private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - - protected Attributes addAttributes(SortedMap 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 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 read(Reader reader) throws IOException { - SortedMap res = new TreeMap(); - try { - List lines = new ArrayList<>(); - try (BufferedReader br = new BufferedReader(reader)) { - String line; - while ((line = br.readLine()) != null) { - lines.add(line); - } - } - if (lines.size() == 0) - return res; - // add an empty new line since the last line is not checked - if (!lines.get(lines.size() - 1).equals("")) - lines.add(""); - - LdapName currentDn = null; - Attributes currentAttributes = null; - StringBuilder currentEntry = new StringBuilder(); - - readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { - String line = lines.get(lineNumber); - boolean isLastLine = false; - if (lineNumber == lines.size() - 1) - isLastLine = true; - if (line.startsWith(" ")) { - currentEntry.append(line.substring(1)); - if (!isLastLine) - continue readLines; - } - - if (currentEntry.length() != 0 || isLastLine) { - // read previous attribute - StringBuilder attrId = new StringBuilder(8); - boolean isBase64 = false; - readAttrId: for (int i = 0; i < currentEntry.length(); i++) { - char c = currentEntry.charAt(i); - if (c == ':') { - if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':') - isBase64 = true; - currentEntry.delete(0, i + (isBase64 ? 2 : 1)); - break readAttrId; - } else { - attrId.append(c); - } - } - - String attributeId = attrId.toString(); - // TODO should we really trim the end of the string as well? - String cleanValueStr = currentEntry.toString().trim(); - Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr; - - // manage DN attributes - if (attributeId.equals(LdapAttrs.DN) || isLastLine) { - if (currentDn != null) { - // - // ADD - // - Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes); - if (previous != null) { -// log.warn("There was already an entry with DN " + currentDn -// + ", which has been discarded by a subsequent one."); - } - } - - if (attributeId.equals(LdapAttrs.DN)) - try { - currentDn = new LdapName(attributeValue.toString()); - currentAttributes = new BasicAttributes(true); - } catch (InvalidNameException e) { -// log.error(attributeValue + " not a valid DN, skipping the entry."); - currentDn = null; - currentAttributes = null; - } - } - - // store attribute - if (currentAttributes != null) { - Attribute attribute = currentAttributes.get(attributeId); - if (attribute == null) { - attribute = new BasicAttribute(attributeId); - currentAttributes.put(attribute); - } - attribute.add(attributeValue); - } - currentEntry = new StringBuilder(); - } - currentEntry.append(line); - } - } finally { - try { - reader.close(); - } catch (IOException e) { - // silent - } - } - return res; - } -} \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java b/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java deleted file mode 100644 index 457380b9b..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.argeo.util.naming; - -import static org.argeo.util.naming.LdapAttrs.DN; -import static org.argeo.util.naming.LdapAttrs.member; -import static org.argeo.util.naming.LdapAttrs.objectClass; -import static org.argeo.util.naming.LdapAttrs.uniqueMember; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.osgi.useradmin.UserDirectoryException; - -/** Basic LDIF writer */ -public class LdifWriter { - private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private final Writer writer; - - /** Writer must be closed by caller */ - public LdifWriter(Writer writer) { - this.writer = writer; - } - - /** Stream must be closed by caller */ - public LdifWriter(OutputStream out) { - this(new OutputStreamWriter(out, DEFAULT_CHARSET)); - } - - public void writeEntry(LdapName name, Attributes attributes) throws IOException { - try { - // check consistency - Rdn nameRdn = name.getRdn(name.size() - 1); - Attribute nameAttr = attributes.get(nameRdn.getType()); - if (!nameAttr.get().equals(nameRdn.getValue())) - throw new UserDirectoryException( - "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name); - - writer.append(DN + ": ").append(name.toString()).append('\n'); - Attribute objectClassAttr = attributes.get(objectClass.name()); - if (objectClassAttr != null) - writeAttribute(objectClassAttr); - attributes: for (NamingEnumeration 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 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 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 values = new TreeSet<>(); - for (NamingEnumeration attrValues = attribute.getAll(); attrValues.hasMore();) { - String value = attrValues.next().toString(); - values.add(value); - } - - for (String value : values) { - writer.append(attribute.getID()).append(": ").append(value).append('\n'); - } - } -} diff --git a/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java b/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java deleted file mode 100644 index ff4ed31b4..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.argeo.util.naming; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class NamingUtils { - /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */ - private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX") - .withZone(ZoneOffset.UTC); - - /** @return null if not parseable */ - public static Instant ldapDateToInstant(String ldapDate) { - try { - return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant(); - } catch (DateTimeParseException e) { - return null; - } - } - - /** @return null if not parseable */ - public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) { - try { - return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime(); - } catch (DateTimeParseException e) { - return null; - } - } - - public static Calendar ldapDateToCalendar(String ldapDate) { - OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate); - GregorianCalendar calendar = new GregorianCalendar(); - calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH)); - calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR)); - calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR)); - return calendar; - } - - public static String instantToLdapDate(ZonedDateTime instant) { - return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC)); - } - - public static String getQueryValue(Map> query, String key) { - if (!query.containsKey(key)) - return null; - List 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> queryToMap(URI uri) { - return queryToMap(uri.getQuery()); - } - - private static Map> queryToMap(String queryPart) { - try { - final Map> query_pairs = new LinkedHashMap>(); - 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()); - } - final String value = idx > 0 && pair.length() > idx + 1 - ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) - : null; - query_pairs.get(key).add(value); - } - return query_pairs; - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e); - } - } - - private NamingUtils() { - - } - - public static void main(String args[]) { - ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC); - String str = utcLdapDate.format(now); - System.out.println(str); - utcLdapDate.parse(str); - utcLdapDate.parse("19520512000000Z"); - } -} diff --git a/org.argeo.util/src/org/argeo/util/naming/NodeOID.java b/org.argeo.util/src/org/argeo/util/naming/NodeOID.java deleted file mode 100644 index ea163d6a4..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/NodeOID.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.argeo.util.naming; - -interface NodeOID { - String BASE = "1.3.6.1.4.1" + ".48308" + ".1"; - - // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308 - String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb"; - - // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308 - String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27"; - - // ATTRIBUTE TYPES - String ATTRIBUTE_TYPES = BASE + ".4"; - - // OBJECT CLASSES - String OBJECT_CLASSES = BASE + ".6"; -} diff --git a/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java b/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java deleted file mode 100644 index 7f0575407..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.util.naming; - -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; - -public class SharedSecret extends AuthPassword { - public final static String X_SHARED_SECRET = "X-SharedSecret"; - private final Instant expiry; - - public SharedSecret(String authInfo, String authValue) { - super(authInfo, authValue); - expiry = null; - } - - public SharedSecret(AuthPassword authPassword) { - super(authPassword); - String authInfo = getAuthInfo(); - if (authInfo.length() == 16) { - expiry = NamingUtils.ldapDateToInstant(authInfo); - } else { - expiry = null; - } - } - - public SharedSecret(ZonedDateTime expiryTimestamp, String value) { - super(NamingUtils.instantToLdapDate(expiryTimestamp), value); - expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant(); - } - - public SharedSecret(int hours, String value) { - this(ZonedDateTime.now().plusHours(hours), value); - } - - @Override - protected String getExpectedAuthScheme() { - return X_SHARED_SECRET; - } - - public boolean isExpired() { - if (expiry == null) - return false; - return expiry.isBefore(Instant.now()); - } - -} diff --git a/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java b/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java deleted file mode 100644 index 22f2a2d69..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.argeo.util.naming; - -/** - * A name which has been specified and for which an id has been defined - * (typically an OID). - */ -public interface SpecifiedName { - /** The name */ - String name(); - - /** An RFC or the URLof some specification */ - default String getSpec() { - return null; - } - - /** Typically an OID */ - default String getID() { - return getClass().getName() + "." + name(); - } -} diff --git a/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java b/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java deleted file mode 100644 index f2476a930..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.argeo.util.naming; - -class SrvRecord implements Comparable { - private final Integer priority; - private final Integer weight; - private final Integer port; - private final String hostname; - - public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) { - this.priority = priority; - this.weight = weight; - this.port = port; - this.hostname = hostname; - } - - @Override - public int compareTo(SrvRecord other) { - // https: // en.wikipedia.org/wiki/SRV_record - if (priority != other.priority) - return priority - other.priority; - if (weight != other.weight) - return other.weight - other.weight; - String host = toHost(false); - String otherHost = other.toHost(false); - if (host.length() == otherHost.length()) - return host.compareTo(otherHost); - else - return host.length() - otherHost.length(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof SrvRecord) { - SrvRecord other = (SrvRecord) obj; - return priority == other.priority && weight == other.weight && port == other.port - && hostname.equals(other.hostname); - } - return false; - } - - @Override - public String toString() { - return priority + " " + weight; - } - - public String toHost(boolean withPort) { - String hostStr = hostname; - if (hostname.charAt(hostname.length() - 1) == '.') - hostStr = hostname.substring(0, hostname.length() - 1); - return hostStr + (withPort ? ":" + port : ""); - } -} diff --git a/org.argeo.util/src/org/argeo/util/naming/package-info.java b/org.argeo.util/src/org/argeo/util/naming/package-info.java deleted file mode 100644 index f62af365e..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic naming and LDAP support. */ -package org.argeo.util.naming; \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/util/package-info.java b/org.argeo.util/src/org/argeo/util/package-info.java deleted file mode 100644 index 4354b0a14..000000000 --- a/org.argeo.util/src/org/argeo/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Java utilities. */ -package org.argeo.util; \ No newline at end of file diff --git a/org.argeo.util/src/org/argeo/util/register/Component.java b/org.argeo.util/src/org/argeo/util/register/Component.java deleted file mode 100644 index 4a812f83b..000000000 --- a/org.argeo.util/src/org/argeo/util/register/Component.java +++ /dev/null @@ -1,283 +0,0 @@ -package org.argeo.util.register; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - * A wrapper for an object, whose dependencies and life cycle can be managed. - */ -public class Component { - - private final I instance; - - private final Runnable init; - private final Runnable close; - - private final Map, PublishedType> types; - private final Set> dependencies; - - private CompletableFuture activationStarted = null; - private CompletableFuture activated = null; - - private CompletableFuture deactivationStarted = null; - private CompletableFuture deactivated = null; - - private Set> dependants = new HashSet<>(); - - Component(Consumer> register, I instance, Runnable init, Runnable close, - Set> dependencies, Set> 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, PublishedType> types = new HashMap<>(classes.size()); - for (Class 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(); - activated = activationStarted // - .thenComposeAsync(this::dependenciesActivated) // - .thenRun(this.init) // - .thenRun(() -> prepareNextDeactivation()); - } - - private void prepareNextDeactivation() { - deactivationStarted = new CompletableFuture(); - deactivated = deactivationStarted // - .thenComposeAsync(this::dependantsDeactivated) // - .thenRun(this.close) // - .thenRun(() -> prepareNextActivation()); - } - - public CompletableFuture getActivated() { - return activated; - } - - public CompletableFuture 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 dependenciesActivated(Void v) { - Set> constraints = new HashSet<>(this.dependencies.size()); - for (Dependency dependency : this.dependencies) { - CompletableFuture dependencyActivated = dependency.publisherActivated() // - .thenCompose(dependency::set); - constraints.add(dependencyActivated); - } - return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()])); - } - - CompletableFuture dependantsDeactivated(Void v) { - Set> constraints = new HashSet<>(this.dependants.size()); - for (Dependency dependant : this.dependants) { - CompletableFuture dependantDeactivated = dependant.dependantDeactivated() // - .thenCompose(dependant::unset); - constraints.add(dependantDeactivated); - } - CompletableFuture dependantsDeactivated = CompletableFuture - .allOf(constraints.toArray(new CompletableFuture[constraints.size()])); - return dependantsDeactivated; - - } - - void addDependant(Dependency dependant) { - dependants.add(dependant); - } - - I getInstance() { - return instance; - } - - @SuppressWarnings("unchecked") - PublishedType getType(Class clss) { - if (!types.containsKey(clss)) - throw new IllegalArgumentException(clss.getName() + " is not a type published by this component"); - return (PublishedType) types.get(clss); - } - - boolean isPublishedType(Class clss) { - return types.containsKey(clss); - } - - public static class PublishedType { - private Component component; - private Class clss; - -// private CompletableFuture> publisherAvailable; - private CompletableFuture value; - - public PublishedType(Component component, Class clss) { - this.clss = clss; - this.component = component; - value = CompletableFuture.completedFuture((T) component.instance); -// value = publisherAvailable.thenApply((c) -> c.getInstance()); - } - - Component getPublisher() { - return component; - } - -// CompletableFuture> publisherAvailable() { -// return publisherAvailable; -// } - - Class getType() { - return clss; - } - } - - public static class Builder { - private final I instance; - - private Runnable init; - private Runnable close; - - private Set> dependencies = new HashSet<>(); - private Set> types = new HashSet<>(); - - public Builder(I instance) { - this.instance = instance; - } - - public Component build(Consumer> register) { - // default values - if (types.isEmpty()) { - types.add(getInstanceClass()); - } - - if (init == null) - init = () -> { - }; - if (close == null) - close = () -> { - }; - - // instantiation - Component component = new Component(register, instance, init, close, dependencies, types); - for (Dependency dependency : dependencies) { - dependency.type.getPublisher().addDependant(dependency); - } - return component; - } - - public Builder addType(Class clss) { - types.add(clss); - return this; - } - - public Builder addInit(Runnable init) { - if (this.init != null) - throw new IllegalArgumentException("init method is already set"); - this.init = init; - return this; - } - - public Builder addClose(Runnable close) { - if (this.close != null) - throw new IllegalArgumentException("close method is already set"); - this.close = close; - return this; - } - - public Builder addDependency(PublishedType type, Consumer set, Consumer unset) { - dependencies.add(new Dependency(type, set, unset)); - return this; - } - - public I get() { - return instance; - } - - @SuppressWarnings("unchecked") - private Class getInstanceClass() { - return (Class) instance.getClass(); - } - - } - - static class Dependency { - private PublishedType type; - private Consumer set; - private Consumer unset; - - // live - Component dependantComponent; - CompletableFuture setStage; - CompletableFuture unsetStage; - - public Dependency(PublishedType types, Consumer set, Consumer 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 publisherActivated() { - return type.getPublisher().activated.copy(); - } - - CompletableFuture dependantDeactivated() { - return dependantComponent.deactivated.copy(); - } - - CompletableFuture set(Void v) { - return type.value.thenAccept(set); - } - - CompletableFuture unset(Void v) { - return type.value.thenAccept(unset); - } - - } -} diff --git a/org.argeo.util/src/org/argeo/util/register/Register.java b/org.argeo.util/src/org/argeo/util/register/Register.java deleted file mode 100644 index 17062809e..000000000 --- a/org.argeo.util/src/org/argeo/util/register/Register.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.util.register; - -import java.util.Map; - -/** A dynamic register of objects. */ -public interface Register { - Singleton set(T obj, Class clss, Map attributes, Class... classes); - - void shutdown(); -} diff --git a/org.argeo.util/src/org/argeo/util/register/Singleton.java b/org.argeo.util/src/org/argeo/util/register/Singleton.java deleted file mode 100644 index 5d70e9aeb..000000000 --- a/org.argeo.util/src/org/argeo/util/register/Singleton.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.util.register; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Future; -import java.util.function.Consumer; - -public class Singleton { - private final Class clss; - private final CompletableFuture registrationStage; - private final List> unregistrationHooks = new ArrayList<>(); - - public Singleton(Class clss, CompletableFuture registrationStage) { - this.clss = clss; - this.registrationStage = registrationStage; - } - - CompletionStage getRegistrationStage() { - return registrationStage.minimalCompletionStage(); - } - - public void addUnregistrationHook(Consumer todo) { - unregistrationHooks.add(todo); - } - - public Future getValue() { - return registrationStage.copy(); - } - - public CompletableFuture prepareUnregistration(Void v) { - List> lst = new ArrayList<>(); - for (Consumer hook : unregistrationHooks) { - lst.add(registrationStage.thenAcceptAsync(hook)); - } - CompletableFuture prepareUnregistrationStage = CompletableFuture - .allOf(lst.toArray(new CompletableFuture[lst.size()])); - return prepareUnregistrationStage; - } -} diff --git a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java b/org.argeo.util/src/org/argeo/util/register/StaticRegister.java deleted file mode 100644 index c186aff08..000000000 --- a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.argeo.util.register; - -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** A minimal component register. */ -public class StaticRegister { - private final static AtomicBoolean started = new AtomicBoolean(false); - private final static IdentityHashMap> components = new IdentityHashMap<>(); - - public static Consumer> asConsumer() { - return (c) -> registerComponent(c); - } - -// public static BiFunction, Predicate>, Component> asProvider() { -// -// } - - static synchronized Component find(Class clss, Predicate> filter) { - Set> result = new HashSet<>(); - instances: for (Object instance : components.keySet()) { - if (!clss.isAssignableFrom(instance.getClass())) - continue instances; - Component component = (Component) 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> 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> constraints = new HashSet<>(); - for (Component component : components.values()) { - component.startDeactivating(); - constraints.add(component.getDeactivated()); - } - - // wait - try { - CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false)) - .get(); - } catch (ExecutionException | InterruptedException e) { - throw new RuntimeException(e); - } - } - - synchronized static void clear() { - components.clear(); - } - -} diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/.classpath b/osgi/equinox/org.argeo.cms.lib.equinox/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/.gitignore b/osgi/equinox/org.argeo.cms.lib.equinox/.gitignore new file mode 100644 index 000000000..09e3bc9b2 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/target/ diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/.project b/osgi/equinox/org.argeo.cms.lib.equinox/.project new file mode 100644 index 000000000..c551d5dff --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.lib.equinox + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/META-INF/.gitignore b/osgi/equinox/org.argeo.cms.lib.equinox/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml b/osgi/equinox/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml new file mode 100644 index 000000000..6a1336220 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd b/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd new file mode 100644 index 000000000..2c83158e2 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd @@ -0,0 +1,2 @@ +Service-Component: \ +OSGI-INF/jettyServiceFactory.xml,\ diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/build.properties b/osgi/equinox/org.argeo.cms.lib.equinox/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java new file mode 100644 index 000000000..e6595a05e --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java @@ -0,0 +1,151 @@ +package org.argeo.cms.equinox.http.jetty; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +import org.argeo.cms.jetty.CmsJettyServer; +import org.eclipse.equinox.http.servlet.HttpServiceServlet; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.osgi.framework.Constants; + +/** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */ +public class EquinoxJettyServer extends CmsJettyServer { + private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader"; + + @Override + protected void addServlets(ServletContextHandler rootContextHandler) throws ServletException { + ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet()); + holder.setInitOrder(0); + holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$ + holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$ + + rootContextHandler.addServlet(holder, "/*"); + + // post-start + SessionHandler sessionManager = rootContextHandler.getSessionHandler(); + sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet()); + } + + public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet { + private final Servlet httpServiceServlet = new HttpServiceServlet(); + private ClassLoader contextLoader; + private final Method sessionDestroyed; + private final Method sessionIdChanged; + + public InternalHttpServiceServlet() { + Class clazz = httpServiceServlet.getClass(); + + try { + sessionDestroyed = clazz.getMethod("sessionDestroyed", new Class[] { String.class }); //$NON-NLS-1$ + } catch (Exception e) { + throw new IllegalStateException(e); + } + try { + sessionIdChanged = clazz.getMethod("sessionIdChanged", new Class[] { String.class }); //$NON-NLS-1$ + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public void init(ServletConfig config) throws ServletException { + ServletContext context = config.getServletContext(); + contextLoader = (ClassLoader) context.getAttribute(INTERNAL_CONTEXT_CLASSLOADER); + + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.init(config); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public void destroy() { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.destroy(); + } finally { + thread.setContextClassLoader(current); + } + contextLoader = null; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + httpServiceServlet.service(req, res); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public ServletConfig getServletConfig() { + return httpServiceServlet.getServletConfig(); + } + + @Override + public String getServletInfo() { + return httpServiceServlet.getServletInfo(); + } + + @Override + public void sessionCreated(HttpSessionEvent event) { + // Nothing to do. + } + + @Override + public void sessionDestroyed(HttpSessionEvent event) { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + sessionDestroyed.invoke(httpServiceServlet, event.getSession().getId()); + } catch (IllegalAccessException | IllegalArgumentException e) { + // not likely + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(current); + } + } + + @Override + public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) { + Thread thread = Thread.currentThread(); + ClassLoader current = thread.getContextClassLoader(); + thread.setContextClassLoader(contextLoader); + try { + sessionIdChanged.invoke(httpServiceServlet, oldSessionId); + } catch (IllegalAccessException | IllegalArgumentException e) { + // not likely + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } finally { + thread.setContextClassLoader(current); + } + } + } + +} diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java new file mode 100644 index 000000000..2cd600152 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java @@ -0,0 +1,231 @@ +package org.argeo.cms.servlet.internal.jetty; + +import java.io.File; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.ForkJoinPool; + +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.CmsDeployProperty; +import org.argeo.cms.util.LangUtils; +import org.argeo.cms.websocket.server.CmsWebSocketConfigurator; +import org.argeo.cms.websocket.server.TestEndpoint; +import org.eclipse.equinox.http.jetty.JettyConfigurator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +public class JettyConfig { + private final static CmsLog log = CmsLog.getLog(JettyConfig.class); + + final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; + + private CmsState cmsState; + + private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext(); + + // private static final String JETTY_PROPERTY_PREFIX = + // "org.eclipse.equinox.http.jetty."; + + public void start() { + // We need to start asynchronously so that Jetty bundle get started by lazy init + // due to the non-configurable behaviour of its activator + ForkJoinPool.commonPool().execute(() -> { + Dictionary properties = getHttpServerConfig(); + startServer(properties); + }); + + ServiceTracker serverSt = new ServiceTracker( + bc, ServerContainer.class, null) { + + @Override + public ServerContainer addingService(ServiceReference reference) { + ServerContainer serverContainer = super.addingService(reference); + + BundleContext bc = reference.getBundle().getBundleContext(); + ServiceReference srConfigurator = bc + .getServiceReference(ServerEndpointConfig.Configurator.class); + ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator); + ServerEndpointConfig config = ServerEndpointConfig.Builder + .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build(); + try { + serverContainer.addEndpoint(config); + } catch (DeploymentException e) { + throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); + } + return serverContainer; + } + + }; + serverSt.open(); + + // check initialisation +// ServiceTracker httpSt = new ServiceTracker(bc, HttpService.class, null) { +// +// @Override +// public HttpService addingService(ServiceReference sr) { +// Object httpPort = sr.getProperty("http.port"); +// Object httpsPort = sr.getProperty("https.port"); +// log.info(httpPortsMsg(httpPort, httpsPort)); +// close(); +// return super.addingService(sr); +// } +// }; +// httpSt.open(); + } + + public void stop() { + try { + JettyConfigurator.stopServer(CmsConstants.DEFAULT); + } catch (Exception e) { + log.error("Cannot stop default Jetty server.", e); + } + + } + + public void startServer(Dictionary properties) { + // Explicitly configures Jetty so that the default server is not started by the + // activator of the Equinox Jetty bundle. + Map config = LangUtils.dictToStringMap(properties); + if (!config.isEmpty()) { + config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS); + + // TODO centralise with Jetty extender + Object webSocketEnabled = config.get(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty()); + if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { + bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); + // config.put(WEBSOCKET_ENABLED, "true"); + } + } + + properties.put(Constants.SERVICE_PID, "default"); + File jettyWorkDir = new File(bc.getDataFile(""), "jettywork"); //$NON-NLS-1$ + jettyWorkDir.mkdir(); + +// HttpServerManager serverManager = new HttpServerManager(jettyWorkDir); +// try { +// serverManager.updated("default", properties); +// } catch (ConfigurationException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + Object httpPort = config.get(JettyHttpConstants.HTTP_PORT); + Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT); + log.info(httpPortsMsg(httpPort, httpsPort)); + +// long begin = System.currentTimeMillis(); +// int tryCount = 60; +// try { +// while (tryCount > 0) { +// try { +// // FIXME deal with multiple ids +// JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config)); +// +// Object httpPort = config.get(JettyHttpConstants.HTTP_PORT); +// Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT); +// log.info(httpPortsMsg(httpPort, httpsPort)); +// +// // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi +// // configuration is not cleaned +// FrameworkUtil.getBundle(JettyConfigurator.class).start(); +// return; +// } catch (IllegalStateException e) { +// // e.printStackTrace(); +// // Jetty may not be ready +// try { +// Thread.sleep(1000); +// } catch (Exception e1) { +// // silent +// } +// tryCount--; +// } +// } +// long duration = System.currentTimeMillis() - begin; +// log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s"); +// } catch (Exception e) { +// log.error("Cannot start default Jetty server with config " + properties, e); +// } + + } + + private String httpPortsMsg(Object httpPort, Object httpsPort) { + return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : ""); + } + + /** Override the provided config with the framework properties */ + public Dictionary getHttpServerConfig() { + String httpPort = getFrameworkProp(CmsDeployProperty.HTTP_PORT); + String httpsPort = getFrameworkProp(CmsDeployProperty.HTTPS_PORT); + /// TODO make it more generic + String httpHost = getFrameworkProp(CmsDeployProperty.HOST); +// String httpsHost = getFrameworkProp( +// JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST); + String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED); + + final Hashtable props = new Hashtable(); + // try { + if (httpPort != null || httpsPort != null) { + boolean httpEnabled = httpPort != null; + props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled); + boolean httpsEnabled = httpsPort != null; + props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled); + + if (httpEnabled) { + props.put(JettyHttpConstants.HTTP_PORT, httpPort); + if (httpHost != null) + props.put(JettyHttpConstants.HTTP_HOST, httpHost); + } + + if (httpsEnabled) { + props.put(JettyHttpConstants.HTTPS_PORT, httpsPort); + if (httpHost != null) + props.put(JettyHttpConstants.HTTPS_HOST, httpHost); + + // keystore + props.put(JettyHttpConstants.SSL_KEYSTORETYPE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE)); + props.put(JettyHttpConstants.SSL_KEYSTORE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE)); + props.put(JettyHttpConstants.SSL_PASSWORD, getFrameworkProp(CmsDeployProperty.SSL_PASSWORD)); + + // truststore + props.put(JettyHttpConstants.SSL_TRUSTSTORETYPE, + getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE)); + props.put(JettyHttpConstants.SSL_TRUSTSTORE, getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE)); + props.put(JettyHttpConstants.SSL_TRUSTSTOREPASSWORD, + getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD)); + + // client certificate authentication + String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH); + if (wantClientAuth != null) + props.put(JettyHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth)); + String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH); + if (needClientAuth != null) + props.put(JettyHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth)); + } + + // web socket + if (webSocketEnabled != null && webSocketEnabled.equals("true")) + props.put(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty(), true); + + props.put(CmsConstants.CN, CmsConstants.DEFAULT); + } + return props; + } + + private String getFrameworkProp(CmsDeployProperty deployProperty) { + return cmsState.getDeployProperty(deployProperty.getProperty()); + } + + public void setCmsState(CmsState cmsState) { + this.cmsState = cmsState; + } + +} diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java new file mode 100644 index 000000000..8ceb358dd --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java @@ -0,0 +1,25 @@ +package org.argeo.cms.servlet.internal.jetty; + +/** Compatible with Jetty. */ +interface JettyHttpConstants { + static final String HTTP_ENABLED = "http.enabled"; + static final String HTTP_PORT = "http.port"; + static final String HTTP_HOST = "http.host"; + static final String HTTPS_ENABLED = "https.enabled"; + static final String HTTPS_HOST = "https.host"; + static final String HTTPS_PORT = "https.port"; + static final String SSL_KEYSTORE = "ssl.keystore"; + static final String SSL_PASSWORD = "ssl.password"; + static final String SSL_KEYPASSWORD = "ssl.keypassword"; + static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth"; + static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth"; + static final String SSL_PROTOCOL = "ssl.protocol"; + static final String SSL_ALGORITHM = "ssl.algorithm"; + static final String SSL_KEYSTORETYPE = "ssl.keystoretype"; + + // Argeo + static final String SSL_TRUSTSTORE = "ssl.truststore"; + static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword"; + static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype"; + +} diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java new file mode 100644 index 000000000..7be23fc0f --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java @@ -0,0 +1,65 @@ +package org.argeo.equinox.jetty; + +import java.util.Dictionary; + +import javax.servlet.ServletContext; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; + +import org.eclipse.equinox.http.jetty.JettyCustomizer; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** Customises the Jetty HTTP server. */ +public class CmsJettyCustomizer extends JettyCustomizer { + static final String SSL_TRUSTSTORE = "ssl.truststore"; + static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword"; + static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype"; + + private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext(); + + public final static String WEBSOCKET_ENABLED = "argeo.websocket.enabled"; + + @Override + public Object customizeContext(Object context, Dictionary settings) { + // WebSocket + Object webSocketEnabled = settings.get(WEBSOCKET_ENABLED); + if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { + ServletContextHandler servletContextHandler = (ServletContextHandler) context; + JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { + + @Override + public void accept(ServletContext servletContext, ServerContainer serverContainer) + throws DeploymentException { + bc.registerService(javax.websocket.server.ServerContainer.class, serverContainer, null); + } + }); + } + return super.customizeContext(context, settings); + + } + + @Override + public Object customizeHttpsConnector(Object connector, Dictionary settings) { + ServerConnector httpsConnector = (ServerConnector) connector; + if (httpsConnector != null) + for (ConnectionFactory connectionFactory : httpsConnector.getConnectionFactories()) { + if (connectionFactory instanceof SslConnectionFactory) { + SslContextFactory.Server sslContextFactory = ((SslConnectionFactory) connectionFactory) + .getSslContextFactory(); + sslContextFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE)); + sslContextFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE)); + sslContextFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD)); + } + } + return super.customizeHttpsConnector(connector, settings); + } + +} diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java new file mode 100644 index 000000000..41c8ce9b0 --- /dev/null +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java @@ -0,0 +1,2 @@ +/** Equinox Jetty extensions. */ +package org.argeo.equinox.jetty; \ No newline at end of file diff --git a/rap/org.argeo.cms.e4.rap/.classpath b/rap/org.argeo.cms.e4.rap/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/rap/org.argeo.cms.e4.rap/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/rap/org.argeo.cms.e4.rap/.project b/rap/org.argeo.cms.e4.rap/.project deleted file mode 100644 index 40c9e013f..000000000 --- a/rap/org.argeo.cms.e4.rap/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.e4.rap - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rap/org.argeo.cms.e4.rap/META-INF/.gitignore b/rap/org.argeo.cms.e4.rap/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rap/org.argeo.cms.e4.rap/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml b/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml deleted file mode 100644 index 1f688baa6..000000000 --- a/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/rap/org.argeo.cms.e4.rap/bnd.bnd b/rap/org.argeo.cms.e4.rap/bnd.bnd deleted file mode 100644 index 5bbe4bc4b..000000000 --- a/rap/org.argeo.cms.e4.rap/bnd.bnd +++ /dev/null @@ -1,9 +0,0 @@ -Bundle-ActivationPolicy: lazy -Service-Component: OSGI-INF/cms-admin-rap.xml - -Import-Package: org.eclipse.swt,\ -org.eclipse.swt.graphics,\ -org.eclipse.e4.ui.workbench,\ -org.eclipse.rap.rwt.client,\ -org.eclipse.nebula.widgets.richtext;resolution:=optional,\ -* diff --git a/rap/org.argeo.cms.e4.rap/build.properties b/rap/org.argeo.cms.e4.rap/build.properties deleted file mode 100644 index c58ea2178..000000000 --- a/rap/org.argeo.cms.e4.rap/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - OSGI-INF/ diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java deleted file mode 100644 index 5fe22ae33..000000000 --- a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.argeo.cms.e4.rap; - -import java.util.HashMap; -import java.util.Map; - -import org.argeo.cms.swt.dialogs.CmsFeedback; -import org.eclipse.rap.e4.E4ApplicationConfig; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.application.ExceptionHandler; -import org.eclipse.rap.rwt.client.WebClient; -import org.osgi.framework.BundleContext; - -/** Base class for CMS RAP applications. */ -public abstract class AbstractRapE4App implements ApplicationConfiguration { - private String e4Xmi; - private String path; - private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle"; - - private Map baseProperties = new HashMap(); - - 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 getBaseProperties() { - return baseProperties; - } - -// protected void addEntryPoint(Application application, E4ApplicationConfig config, Map 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 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 customise(String pageTitle) { - Map 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 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 = ""; - } - } - - public void destroy(Map properties) { - - } -} diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java deleted file mode 100644 index 66be1e8e9..000000000 --- a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.argeo.cms.e4.rap; - -import org.eclipse.rap.rwt.application.Application; - -/** - * Access to canonical views of the core CMS concepts, useful for devleopers and - * operators. - */ -public class CmsE4AdminApp extends AbstractRapE4App { - @Override - protected void addEntryPoints(Application application) { - addE4EntryPoint(application, "/devops", "org.argeo.cms.e4/e4xmi/cms-devops.e4xmi", - customise("Argeo CMS DevOps")); - addE4EntryPoint(application, "/ego", "org.argeo.cms.e4/e4xmi/cms-ego.e4xmi", customise("Argeo CMS Ego")); - } - -} diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java deleted file mode 100644 index a5a32348e..000000000 --- a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java +++ /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() { - - @Override - public Integer run() { - // SPNEGO - // HttpServletRequest request = RWT.getRequest(); - // String authorization = request.getHeader(HEADER_AUTHORIZATION); - // if (authorization == null || !authorization.startsWith("Negotiate")) { - // HttpServletResponse response = RWT.getResponse(); - // response.setStatus(401); - // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate"); - // response.setDateHeader("Date", System.currentTimeMillis()); - // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60 - // * 1000)); - // response.setHeader("Accept-Ranges", "bytes"); - // response.setHeader("Connection", "Keep-Alive"); - // response.setHeader("Keep-Alive", "timeout=5, max=97"); - // // response.setContentType("text/html; charset=UTF-8"); - // } - - JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class); - Integer exitCode = ep.createUI(); - jsExecutor.execute("location.reload()"); - return exitCode; - } - - }); - } - }; - return authEp; - } - - protected EntryPoint createEntryPoint() { - return super.create(); - } -} diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java deleted file mode 100644 index 95be53d5f..000000000 --- a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java +++ /dev/null @@ -1,183 +0,0 @@ -package org.argeo.cms.e4.rap; - -import java.security.AccessController; -import java.util.UUID; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.UxContext; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.SimpleSwtUxContext; -import org.argeo.cms.swt.auth.CmsLoginShell; -import org.argeo.cms.swt.dialogs.CmsFeedback; -import org.argeo.cms.ui.util.SimpleImageManager; -import org.eclipse.e4.core.services.events.IEventBroker; -import org.eclipse.e4.ui.workbench.UIEvents; -import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; -import org.eclipse.e4.ui.workbench.lifecycle.PreSave; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.client.service.BrowserNavigation; -import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; -import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventHandler; - -@SuppressWarnings("restriction") -public class CmsLoginLifecycle implements CmsView { - private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class); - - private UxContext uxContext; - private CmsImageManager imageManager; - - private LoginContext loginContext; - private BrowserNavigation browserNavigation; - - private String state = null; - private String uid; - - @PostContextCreate - boolean login(final IEventBroker eventBroker) { - uid = UUID.randomUUID().toString(); - browserNavigation = RWT.getClient().getService(BrowserNavigation.class); - if (browserNavigation != null) - browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() { - private static final long serialVersionUID = -3668136623771902865L; - - @Override - public void navigated(BrowserNavigationEvent event) { - state = event.getState(); - if (uxContext != null)// is logged in - stateChanged(); - } - }); - - Subject subject = Subject.getSubject(AccessController.getContext()); - Display display = Display.getCurrent(); -// UiContext.setData(CmsView.KEY, this); - // FIXME get CMS context - CmsLoginShell loginShell = new CmsLoginShell(this, null); - CmsSwtUtils.registerCmsView(loginShell.getShell(), this); - loginShell.setSubject(subject); - try { - // try pre-auth - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell); - loginContext.login(); - } catch (LoginException e) { - loginShell.createUi(); - loginShell.open(); - - while (!loginShell.getShell().isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - if (CurrentUser.getUsername(getSubject()) == null) - return false; - uxContext = new SimpleSwtUxContext(); - imageManager = new SimpleImageManager(); - - eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() { - @Override - public void handleEvent(Event event) { - startupComplete(); - eventBroker.unsubscribe(this); - } - }); - - // lcs.changeApplicationLocale(Locale.FRENCH); - return true; - } - - @PreSave - void destroy() { - // logout(); - } - - @Override - public UxContext getUxContext() { - return uxContext; - } - - @Override - public void navigateTo(String state) { - browserNavigation.pushState(state, state); - } - - @Override - public void authChange(LoginContext loginContext) { - if (loginContext == null) - throw new IllegalArgumentException("Login context cannot be null"); - // logout previous login context - // if (this.loginContext != null) - // try { - // this.loginContext.logout(); - // } catch (LoginException e1) { - // System.err.println("Could not log out: " + e1); - // } - this.loginContext = loginContext; - } - - @Override - public void logout() { - if (loginContext == null) - throw new IllegalStateException("Login context should not be null"); - try { - CurrentUser.logoutCmsSession(loginContext.getSubject()); - loginContext.logout(); - } catch (LoginException e) { - throw new IllegalStateException("Cannot log out", e); - } - } - - @Override - public void exception(Throwable e) { - String msg = "Unexpected exception in Eclipse 4 RAP"; - log.error(msg, e); - CmsFeedback.show(msg, e); - } - - @Override - public CmsImageManager getImageManager() { - return imageManager; - } - - protected Subject getSubject() { - return loginContext.getSubject(); - } - - @Override - public boolean isAnonymous() { - return CurrentUser.isAnonymous(getSubject()); - } - - @Override - public String getUid() { - return uid; - } - - // CALLBACKS - protected void startupComplete() { - } - - protected void stateChanged() { - - } - - // GETTERS - protected BrowserNavigation getBrowserNavigation() { - return browserNavigation; - } - - protected String getState() { - return state; - } - -} diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java deleted file mode 100644 index 1bca333c9..000000000 --- a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms.e4.rap; - -import java.util.Enumeration; - -import org.apache.commons.io.FilenameUtils; -import org.argeo.api.cms.CmsLog; -import org.eclipse.rap.rwt.application.Application; -import org.osgi.framework.Bundle; - -/** Simple RAP app which loads all e4xmi files. */ -public class SimpleRapE4App extends AbstractRapE4App { - private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class); - - private String baseE4xmi = "/e4xmi"; - - @Override - protected void addEntryPoints(Application application) { - Bundle bundle = getBundleContext().getBundle(); - Enumeration paths = bundle.getEntryPaths(baseE4xmi); - while (paths.hasMoreElements()) { - String p = paths.nextElement(); - if (p.endsWith(".e4xmi")) { - String e4xmiPath = bundle.getSymbolicName() + '/' + p; - String name = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p)); - addE4EntryPoint(application, name, e4xmiPath, getBaseProperties()); - if (log.isDebugEnabled()) - log.debug("Registered " + e4xmiPath + " as " + getContextName() + name); - } - } - } - -} diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java deleted file mode 100644 index 1122f1922..000000000 --- a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Eclipse 4 RAP specific extensions. */ -package org.argeo.cms.e4.rap; \ No newline at end of file diff --git a/rap/org.argeo.cms.ui.rap/.classpath b/rap/org.argeo.cms.ui.rap/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/rap/org.argeo.cms.ui.rap/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/rap/org.argeo.cms.ui.rap/.project b/rap/org.argeo.cms.ui.rap/.project deleted file mode 100644 index 1a37a6771..000000000 --- a/rap/org.argeo.cms.ui.rap/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.ui.rap - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rap/org.argeo.cms.ui.rap/META-INF/.gitignore b/rap/org.argeo.cms.ui.rap/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rap/org.argeo.cms.ui.rap/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rap/org.argeo.cms.ui.rap/bnd.bnd b/rap/org.argeo.cms.ui.rap/bnd.bnd deleted file mode 100644 index e3e71b309..000000000 --- a/rap/org.argeo.cms.ui.rap/bnd.bnd +++ /dev/null @@ -1,15 +0,0 @@ -Import-Package:\ -org.eclipse.swt,\ -org.argeo.eclipse.ui,\ -javax.jcr.nodetype,\ -javax.jcr.security,\ -org.eclipse.swt.graphics,\ -org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\ -javax.servlet.*;version="[3,5)",\ -* - diff --git a/rap/org.argeo.cms.ui.rap/build.properties b/rap/org.argeo.cms.ui.rap/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/rap/org.argeo.cms.ui.rap/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java deleted file mode 100644 index 01ebb237e..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java +++ /dev/null @@ -1,268 +0,0 @@ -package org.argeo.cms.ui.script; - -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.script.Invocable; -import javax.script.ScriptException; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.Selected; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.util.CmsPane; -import org.argeo.cms.web.SimpleErgonomics; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.EntryPoint; -import org.eclipse.rap.rwt.application.EntryPointFactory; -import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; - -public class AppUi implements CmsUiProvider, Branding { - private final CmsScriptApp app; - - private CmsUiProvider ui; - private String createUi; - private Object impl; - private String script; - // private Branding branding = new Branding(); - - private EntryPointFactory factory; - - // Branding - private String themeId; - private String additionalHeaders; - private String bodyHtml; - private String pageTitle; - private String pageOverflow; - private String favicon; - - public AppUi(CmsScriptApp app) { - this.app = app; - } - - public AppUi(CmsScriptApp app, String scriptPath) { - this.app = app; - this.ui = new ScriptUi((BundleContext) app.getScriptEngine().get(CmsScriptRwtApplication.BC), - app.getScriptEngine(), scriptPath); - } - - public AppUi(CmsScriptApp app, CmsUiProvider uiProvider) { - this.app = app; - this.ui = uiProvider; - } - - public AppUi(CmsScriptApp app, EntryPointFactory factory) { - this.app = app; - this.factory = factory; - } - - public void apply(Repository repository, Application application, Branding appBranding, String path) { - Map 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 properties) { - if (themeId != null) - properties.put(WebClient.THEME_ID, themeId); - if (additionalHeaders != null) - properties.put(WebClient.HEAD_HTML, additionalHeaders); - if (bodyHtml != null) - properties.put(WebClient.BODY_HTML, bodyHtml); - if (pageTitle != null) - properties.put(WebClient.PAGE_TITLE, pageTitle); - if (pageOverflow != null) - properties.put(WebClient.PAGE_OVERFLOW, pageOverflow); - if (favicon != null) - properties.put(WebClient.FAVICON, favicon); - } - - // public Branding getBranding() { - // return branding; - // } - - @Override - public Control createUi(Composite parent, Node context) throws RepositoryException { - CmsPane cmsPane = new CmsPane(parent, SWT.NONE); - - if (false) { - // QA - CmsSwtUtils.style(cmsPane.getQaArea(), "qa"); - Button reload = new Button(cmsPane.getQaArea(), SWT.FLAT); - CmsSwtUtils.style(reload, "qa"); - reload.setText("Reload"); - reload.addSelectionListener(new Selected() { - private static final long serialVersionUID = 1L; - - @Override - public void widgetSelected(SelectionEvent e) { - new Thread() { - @Override - public void run() { - app.reload(); - } - }.start(); - RWT.getClient().getService(JavaScriptExecutor.class) - .execute("setTimeout('location.reload()',1000)"); - } - }); - - // Support - CmsSwtUtils.style(cmsPane.getSupportArea(), "support"); - Label msg = new Label(cmsPane.getSupportArea(), SWT.NONE); - CmsSwtUtils.style(msg, "support"); - msg.setText("UNSUPPORTED DEVELOPMENT VERSION"); - } - - if (ui != null) { - ui.createUi(cmsPane.getMainArea(), context); - } - if (createUi != null) { - Invocable invocable = (Invocable) app.getScriptEngine(); - try { - invocable.invokeFunction(createUi, cmsPane.getMainArea(), context); - - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ScriptException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - if (impl != null) { - Invocable invocable = (Invocable) app.getScriptEngine(); - try { - invocable.invokeMethod(impl, "createUi", cmsPane.getMainArea(), context); - - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ScriptException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - // Invocable invocable = (Invocable) app.getScriptEngine(); - // try { - // invocable.invokeMethod(AppUi.this, "initUi", parent, context); - // - // } catch (NoSuchMethodException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } catch (ScriptException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - - return null; - } - - public void setCreateUi(String createUi) { - this.createUi = createUi; - } - - public void setImpl(Object impl) { - this.impl = impl; - } - - public Object getImpl() { - return impl; - } - - public String getScript() { - return script; - } - - public void setScript(String script) { - this.script = script; - } - - // Branding - public String getThemeId() { - return themeId; - } - - public void setThemeId(String themeId) { - this.themeId = themeId; - } - - public String getAdditionalHeaders() { - return additionalHeaders; - } - - public void setAdditionalHeaders(String additionalHeaders) { - this.additionalHeaders = additionalHeaders; - } - - public String getBodyHtml() { - return bodyHtml; - } - - public void setBodyHtml(String bodyHtml) { - this.bodyHtml = bodyHtml; - } - - public String getPageTitle() { - return pageTitle; - } - - public void setPageTitle(String pageTitle) { - this.pageTitle = pageTitle; - } - - public String getPageOverflow() { - return pageOverflow; - } - - public void setPageOverflow(String pageOverflow) { - this.pageOverflow = pageOverflow; - } - - public String getFavicon() { - return favicon; - } - - public void setFavicon(String favicon) { - this.favicon = favicon; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java deleted file mode 100644 index f72338ef7..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.argeo.cms.ui.script; - -import java.util.Map; - -public interface Branding { - public void applyBranding(Map properties); - - public String getThemeId(); - - public String getAdditionalHeaders(); - - public String getBodyHtml(); - - public String getPageTitle(); - - public String getPageOverflow(); - - public String getFavicon(); - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java deleted file mode 100644 index edf558e9c..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java +++ /dev/null @@ -1,421 +0,0 @@ -package org.argeo.cms.ui.script; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.script.ScriptEngine; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsTheme; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.argeo.cms.web.BundleResourceLoader; -import org.argeo.cms.web.SimpleErgonomics; -import org.argeo.cms.web.WebThemeUtils; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.application.ExceptionHandler; -import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; -import org.osgi.service.http.HttpContext; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; - -public class CmsScriptApp implements Branding { - public final static String CONTEXT_NAME = "contextName"; - - ServiceRegistration 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 resources = new ArrayList<>(); - - private Map 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 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 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 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 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 getUi() { - return ui; - } - - public void setUi(Map ui) { - this.ui = ui; - } - - // Branding - public String getThemeId() { - return themeId; - } - - public void setThemeId(String themeId) { - this.themeId = themeId; - } - - public String getAdditionalHeaders() { - return additionalHeaders; - } - - public void setAdditionalHeaders(String additionalHeaders) { - this.additionalHeaders = additionalHeaders; - } - - public String getBodyHtml() { - return bodyHtml; - } - - public void setBodyHtml(String bodyHtml) { - this.bodyHtml = bodyHtml; - } - - public String getPageTitle() { - return pageTitle; - } - - public void setPageTitle(String pageTitle) { - this.pageTitle = pageTitle; - } - - public String getPageOverflow() { - return pageOverflow; - } - - public void setPageOverflow(String pageOverflow) { - this.pageOverflow = pageOverflow; - } - - public String getFavicon() { - return favicon; - } - - public void setFavicon(String favicon) { - this.favicon = favicon; - } - - public CmsUiProvider getHeader() { - return header; - } - - public void setHeader(CmsUiProvider header) { - this.header = header; - } - - public Integer getHeaderHeight() { - return headerHeight; - } - - public void setHeaderHeight(Integer headerHeight) { - this.headerHeight = headerHeight; - } - - public CmsUiProvider getLead() { - return lead; - } - - public void setLead(CmsUiProvider lead) { - this.lead = lead; - } - - public CmsUiProvider getEnd() { - return end; - } - - public void setEnd(CmsUiProvider end) { - this.end = end; - } - - public CmsUiProvider getFooter() { - return footer; - } - - public void setFooter(CmsUiProvider footer) { - this.footer = footer; - } - - static class BundleHttpContext implements HttpContext { - private BundleContext bundleContext; - - public BundleHttpContext(BundleContext bundleContext) { - super(); - this.bundleContext = bundleContext; - } - - @Override - public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { - // TODO Auto-generated method stub - return true; - } - - @Override - public URL getResource(String name) { - - return bundleContext.getBundle().getEntry(name); - } - - @Override - public String getMimeType(String name) { - return null; - } - - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java deleted file mode 100644 index 499840a73..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.argeo.cms.ui.script; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; - -import javax.jcr.Repository; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.wiring.BundleWiring; - -public class CmsScriptRwtApplication implements ApplicationConfiguration { - public final static String APP = "APP"; - public final static String BC = "BC"; - - private final CmsLog log = CmsLog.getLog(CmsScriptRwtApplication.class); - - BundleContext bundleContext; - Repository repository; - - ScriptEngine engine; - - public void init(BundleContext bundleContext) { - this.bundleContext = bundleContext; - ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader(); - ClassLoader originalCcl = Thread.currentThread().getContextClassLoader(); - try { -// Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager -// ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl); -// engine = scriptEngineManager.getEngineByName("JavaScript"); -// if (engine == null) {// Nashorn -// Thread.currentThread().setContextClassLoader(originalCcl); -// scriptEngineManager = new ScriptEngineManager(); -// Thread.currentThread().setContextClassLoader(bundleCl); -// engine = scriptEngineManager.getEngineByName("JavaScript"); -// } - engine = loadScriptEngine(originalCcl, bundleCl); - - // Load script - URL appUrl = bundleContext.getBundle().getEntry("cms/app.js"); - // System.out.println("Loading " + appUrl); - // System.out.println("Loading " + appUrl.getHost()); - // System.out.println("Loading " + appUrl.getPath()); - - CmsScriptApp app = new CmsScriptApp(engine); - engine.put(APP, app); - engine.put(BC, bundleContext); - try (Reader reader = new InputStreamReader(appUrl.openStream())) { - engine.eval(reader); - } catch (IOException | ScriptException e) { - throw new CmsException("Cannot execute " + appUrl, e); - } - - if (log.isDebugEnabled()) - log.debug("CMS script app initialized from " + appUrl); - - } catch (Exception e) { - e.printStackTrace(); - } finally { - Thread.currentThread().setContextClassLoader(originalCcl); - } - } - - public void destroy(BundleContext bundleContext) { - engine = null; - } - - @Override - public void configure(Application application) { - load(application); - } - - void load(Application application) { - CmsScriptApp app = getApp(); - app.apply(bundleContext, repository, application); - if (log.isDebugEnabled()) - log.debug("CMS script app loaded to " + app.getWebPath()); - } - - CmsScriptApp getApp() { - if (engine == null) - throw new IllegalStateException("CMS script app is not initialized"); - return (CmsScriptApp) engine.get(APP); - } - - void update() { - - try { - bundleContext.getBundle().update(); - } catch (BundleException e) { - e.printStackTrace(); - } - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - private static ScriptEngine loadScriptEngine(ClassLoader originalCcl, ClassLoader bundleCl) { - Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager - ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl); - ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript"); - if (engine == null) {// Nashorn - Thread.currentThread().setContextClassLoader(originalCcl); - scriptEngineManager = new ScriptEngineManager(); - Thread.currentThread().setContextClassLoader(bundleCl); - engine = scriptEngineManager.getEngineByName("JavaScript"); - } - return engine; - } -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java deleted file mode 100644 index a55095371..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.argeo.cms.ui.script; - -import javax.jcr.Repository; - -import org.argeo.api.cms.CmsLog; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; - -public class ScriptAppActivator implements BundleActivator { - private final static CmsLog log = CmsLog.getLog(ScriptAppActivator.class); - - @Override - public void start(BundleContext context) throws Exception { - try { - CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication(); - appConfig.init(context); - CmsScriptApp app = appConfig.getApp(); - ServiceTracker repoSt = new ServiceTracker(context, - FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) { - - @Override - public Repository addingService(ServiceReference reference) { - Repository repository = super.addingService(reference); - appConfig.setRepository(repository); - CmsScriptApp app = appConfig.getApp(); - app.register(context, appConfig); - return repository; - } - - }; - repoSt.open(); - } catch (Exception e) { - log.error("Cannot initialise script bundle " + context.getBundle().getSymbolicName(), e); - throw e; - } - } - - @Override - public void stop(BundleContext context) throws Exception { - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java deleted file mode 100644 index 0c870e1e8..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.argeo.cms.ui.script; - -import java.net.URL; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.script.Invocable; -import javax.script.ScriptEngine; -import javax.script.ScriptException; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.osgi.framework.BundleContext; - -class ScriptUi implements CmsUiProvider { - private final static CmsLog log = CmsLog.getLog(ScriptUi.class); - - private boolean development = true; - private ScriptEngine scriptEngine; - - private URL appUrl; - // private BundleContext bundleContext; - // private String path; - - // private Bindings bindings; - // private String script; - - public ScriptUi(BundleContext bundleContext,ScriptEngine scriptEngine, String path) { - this.scriptEngine = scriptEngine; -//// ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); -// ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader(); -// ClassLoader originalCcl = Thread.currentThread().getContextClassLoader(); -// try { -//// Thread.currentThread().setContextClassLoader(bundleCl); -//// scriptEngine = scriptEngineManager.getEngineByName("JavaScript"); -//// scriptEngine.put(CmsScriptRwtApplication.BC, bundleContext); -// scriptEngine = CmsScriptRwtApplication.loadScriptEngine(originalCcl, bundleCl); -// -// } catch (Exception e) { -// e.printStackTrace(); -// } finally { -// Thread.currentThread().setContextClassLoader(originalCcl); -// } - this.appUrl = bundleContext.getBundle().getEntry(path); - load(); - } - - private void load() { -// try (Reader reader = new InputStreamReader(appUrl.openStream())) { -// scriptEngine.eval(reader); -// } catch (IOException | ScriptException e) { -// log.warn("Cannot execute " + appUrl, e); -// } - - try { - scriptEngine.eval("load('" + appUrl + "')"); - } catch (ScriptException e) { - log.warn("Cannot execute " + appUrl, e); - } - - } - - // public ScriptUiProvider(ScriptEngine scriptEngine, String script) throws - // ScriptException { - // super(); - // this.scriptEngine = scriptEngine; - // this.script = script; - // bindings = scriptEngine.createBindings(); - // scriptEngine.eval(script, bindings); - // } - - @Override - public Control createUi(Composite parent, Node context) throws RepositoryException { - long begin = System.currentTimeMillis(); - // if (bindings == null) { - // bindings = scriptEngine.createBindings(); - // try { - // scriptEngine.eval(script, bindings); - // } catch (ScriptException e) { - // log.warn("Cannot evaluate script", e); - // } - // } - // Bindings bindings = scriptEngine.createBindings(); - // bindings.put("parent", parent); - // bindings.put("context", context); - // URL appUrl = bundleContext.getBundle().getEntry(path); - // try (Reader reader = new InputStreamReader(appUrl.openStream())) { - // scriptEngine.eval(reader,bindings); - // } catch (IOException | ScriptException e) { - // log.warn("Cannot execute " + appUrl, e); - // } - - if (development) - load(); - - Invocable invocable = (Invocable) scriptEngine; - try { - invocable.invokeFunction("createUi", parent, context); - } catch (NoSuchMethodException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ScriptException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - long duration = System.currentTimeMillis() - begin; - if (log.isTraceEnabled()) - log.trace(appUrl + " UI in " + duration + " ms"); - return null; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js deleted file mode 100644 index be9618dcb..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js +++ /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, '' + text + '') -} - -function newText(parent, style, msg) { - var control = new Text(parent, SWT.NONE) - control.setMessage(msg) - CmsUtils.style(control, style) - return control -} - -function newScrolledPage(parent) { - var scrolled = new ScrolledPage(parent, SWT.NONE) - scrolled.setLayoutData(CmsUtils.fillAll()) - scrolled.setLayout(CmsUtils.noSpaceGridLayout()) - var page = new Composite(scrolled, SWT.NONE) - page.setLayout(CmsUtils.noSpaceGridLayout()) - page.setBackgroundMode(SWT.INHERIT_NONE) - return page -} - -function gridData(control) { - var gridData = new GridData() - control.setLayoutData(gridData) - return gridData -} - -function gridData(control, hAlign, vAlign) { - var gridData = new GridData(hAlign, vAlign, false, false) - control.setLayoutData(gridData) - return gridData -} - -// print(__FILE__, __LINE__, __DIR__) diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java deleted file mode 100644 index 7440596ab..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS user interface scripting. */ -package org.argeo.cms.ui.script; \ No newline at end of file diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java deleted file mode 100644 index 947a4d11a..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java +++ /dev/null @@ -1,398 +0,0 @@ -package org.argeo.cms.web; - -import static org.argeo.util.naming.SharedSecret.X_SHARED_SECRET; - -import java.io.IOException; -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.PathNotFoundException; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.http.HttpServletRequest; - -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsAuth; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.RemoteAuthCallback; -import org.argeo.cms.auth.RemoteAuthCallbackHandler; -import org.argeo.cms.servlet.ServletHttpRequest; -import org.argeo.cms.servlet.ServletHttpResponse; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.specific.UiContext; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.naming.AuthPassword; -import org.argeo.util.naming.SharedSecret; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.AbstractEntryPoint; -import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.rap.rwt.client.service.BrowserNavigation; -import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; -import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; -import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** Manages history and navigation */ -@Deprecated -public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implements CmsView { - private static final long serialVersionUID = 906558779562569784L; - - private final CmsLog log = CmsLog.getLog(AbstractCmsEntryPoint.class); - - // private final Subject subject; - private LoginContext loginContext; - - private final Repository repository; - private final String workspace; - private final String defaultPath; - private final Map 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 factoryProperties) { - this.repository = repository; - this.workspace = workspace; - this.defaultPath = defaultPath; - this.factoryProperties = new HashMap(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() { - @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() { - - @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() { - @Override - public Void run() { - refresh(); - return null; - } - }); - } - - /** Sets the state of the entry point and retrieve the related JCR node. */ - protected synchronized String setState(String newState) { - String previousState = this.state; - - String newNodePath = null; - String prefix = null; - this.state = newState; - if (newState.equals("~")) - this.state = ""; - - try { - int firstSlash = state.indexOf('/'); - if (firstSlash == 0) { - newNodePath = state; - prefix = ""; - } else if (firstSlash > 0) { - prefix = state.substring(0, firstSlash); - newNodePath = state.substring(firstSlash); - } else { - newNodePath = defaultPath; - prefix = state; - - } - - // auth - int colonIndex = prefix.indexOf('$'); - if (colonIndex > 0) { - SharedSecret token = new SharedSecret(new AuthPassword(X_SHARED_SECRET + '$' + prefix)) { - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - super.handle(callbacks); - // handle HTTP context - for (Callback callback : callbacks) { - if (callback instanceof RemoteAuthCallback) { - ((RemoteAuthCallback) callback) - .setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); - ((RemoteAuthCallback) callback) - .setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); - } - } - } - }; - LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, token); - lc.login(); - authChange(lc);// sets the node as well - // } else { - // // TODO check consistency - // } - } else { - Node newNode = null; - if (session.nodeExists(newNodePath)) - newNode = session.getNode(newNodePath); - else { -// throw new CmsException("Data " + newNodePath + " does not exist"); - newNode = null; - } - setNode(newNode); - } - String title = publishMetaData(getNode()); - - if (log.isTraceEnabled()) - log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")"); - - return title; - } catch (Exception e) { - log.error("Cannot set state '" + state + "'", e); - if (state.equals("") || newState.equals("~") || newState.equals(previousState)) - return "Unrecoverable exception : " + e.getClass().getSimpleName(); - if (previousState.equals("")) - previousState = "~"; - navigateTo(previousState); - throw new CmsException("Unexpected issue when accessing #" + newState, e); - } - } - - private String publishMetaData(Node node) throws RepositoryException { - // Title - String title; - if (node != null && node.isNodeType(NodeType.MIX_TITLE) && node.hasProperty(Property.JCR_TITLE)) - title = node.getProperty(Property.JCR_TITLE).getString() + " - " + getBaseTitle(); - else - title = getBaseTitle(); - - HttpServletRequest request = UiContext.getHttpRequest(); - if (request == null) - return null; - - StringBuilder js = new StringBuilder(); - if (title == null) - title = ""; - title = title.replace("'", "\\'");// sanitize - js.append("document.title = '" + title + "';"); - jsExecutor.execute(js.toString()); - return title; - } - - // Simply remove some illegal character - // private String clean(String stringToClean) { - // return stringToClean.replaceAll("'", "").replaceAll("\\n", "") - // .replaceAll("\\t", ""); - // } - - protected synchronized Node getNode() { - return node; - } - - private synchronized void setNode(Node node) throws RepositoryException { - this.node = node; - this.nodePath = node == null ? null : node.getPath(); - } - - protected String getState() { - return state; - } - - protected Throwable getException() { - return exception; - } - - protected void resetException() { - exception = null; - } - - protected Session getSession() { - return session; - } - - private class CmsNavigationListener implements BrowserNavigationListener { - private static final long serialVersionUID = -3591018803430389270L; - - @Override - public void navigated(BrowserNavigationEvent event) { - setState(event.getState()); - doRefresh(); - } - } -} \ No newline at end of file diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java deleted file mode 100644 index ca93e625e..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.cms.web; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.osgi.framework.Bundle; - -/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */ -public class BundleResourceLoader implements ResourceLoader { - private final Bundle bundle; - - public BundleResourceLoader(Bundle bundle) { - this.bundle = bundle; - } - - @Override - public InputStream getResourceAsStream(String resourceName) throws IOException { - URL res = bundle.getEntry(resourceName); - if (res == null) { - res = bundle.getResource(resourceName); - if (res == null) - throw new IllegalArgumentException( - "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName()); - } - return res.openStream(); - } - - public Bundle getBundle() { - return bundle; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java deleted file mode 100644 index 5de0f9103..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.web; - -import java.io.IOException; -import java.io.InputStream; - -import org.argeo.api.cms.CmsTheme; -import org.eclipse.rap.rwt.service.ResourceLoader; - -/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */ -public class CmsThemeResourceLoader implements ResourceLoader { - private final CmsTheme theme; - - public CmsThemeResourceLoader(CmsTheme theme) { - super(); - this.theme = theme; - } - - @Override - public InputStream getResourceAsStream(String resourceName) throws IOException { - return theme.getResourceAsStream(resourceName); - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java deleted file mode 100644 index 4008b49eb..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.argeo.cms.web; - -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.argeo.api.cms.CmsApp; -import org.argeo.api.cms.CmsAppListener; -import org.argeo.api.cms.CmsTheme; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.util.LangUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.application.ExceptionHandler; -import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.swt.widgets.Display; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.ServiceRegistration; -import org.osgi.service.event.EventAdmin; - -/** An RWT web app integrating with a {@link CmsApp}. */ -public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener { - private final static CmsLog log = CmsLog.getLog(CmsWebApp.class); - - private BundleContext bundleContext; - private CmsApp cmsApp; - private String cmsAppId; - private EventAdmin eventAdmin; - - private ServiceRegistration 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 properties) { - this.bundleContext = bundleContext; - contextName = properties.get(CONTEXT_NAME); - if (cmsApp != null) { - if (cmsApp.allThemesAvailable()) - publishWebApp(); - } - } - - public void destroy(BundleContext bundleContext, Map 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 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 commonProperties) { - for (String uiName : cmsApp.getUiNames()) { - Map 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 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 properties) { - this.cmsApp = cmsApp; - this.cmsAppId = properties.get(Constants.SERVICE_PID); - this.cmsApp.addCmsAppListener(this); - } - - public void unsetCmsApp(CmsApp cmsApp, Map 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 regProps = LangUtils.dict(CONTEXT_NAME, contextName); - if (rwtAppReg != null) - rwtAppReg.unregister(); - if (bundleContext != null) { - rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps); - if (log.isDebugEnabled()) - log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ..."); - } - } - - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java deleted file mode 100644 index afc07c5b0..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ /dev/null @@ -1,357 +0,0 @@ -package org.argeo.cms.web; - -import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext; - -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsApp; -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.CmsUi; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.UxContext; -import org.argeo.cms.LocaleUtils; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.RemoteAuthCallbackHandler; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.argeo.cms.servlet.ServletHttpRequest; -import org.argeo.cms.servlet.ServletHttpResponse; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.SimpleSwtUxContext; -import org.argeo.cms.swt.dialogs.CmsFeedback; -import org.argeo.cms.ui.util.DefaultImageManager; -import org.argeo.eclipse.ui.specific.UiContext; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.EntryPoint; -import org.eclipse.rap.rwt.client.service.BrowserNavigation; -import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; -import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; -import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle; -import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTError; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventAdmin; - -/** The {@link CmsView} for a {@link CmsWebApp}. */ -@SuppressWarnings("restriction") -public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener { - private static final long serialVersionUID = 7733510691684570402L; - private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class); - - private EventAdmin eventAdmin; - - private final CmsWebApp cmsWebApp; - private final String uiName; - - private LoginContext loginContext; - private String state; - private Throwable exception; - private UxContext uxContext; - private CmsImageManager imageManager; - - private Display display; - private CmsUi ui; - - private String uid; - - // Client services - // private final JavaScriptExecutor jsExecutor; - private final BrowserNavigation browserNavigation; - - /** Experimental OS-like multi windows. */ - private boolean multipleShells = false; - - public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) { - assert cmsWebApp != null; - assert uiName != null; - this.cmsWebApp = cmsWebApp; - this.uiName = uiName; - uid = UUID.randomUUID().toString(); - - // Initial login - LoginContext lc; - try { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, - new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()), - new ServletHttpResponse(UiContext.getHttpResponse()))); - lc.login(); - } catch (LoginException e) { - try { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS); - lc.login(); - } catch (LoginException e1) { - throw new IllegalStateException("Cannot log in as anonymous", e1); - } - } - authChange(lc); - - // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class); - browserNavigation = RWT.getClient().getService(BrowserNavigation.class); - if (browserNavigation != null) - browserNavigation.addBrowserNavigationListener(this); - } - - protected void createContents(Composite parent) { - Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { - @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 doAs(PrivilegedAction 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() { - @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 properties) { - if (properties == null) - properties = new HashMap<>(); - if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid)) - throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid (" - + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid); - properties.put(CMS_VIEW_UID_PROPERTY, uid); - eventAdmin.sendEvent(new Event(topic, properties)); - } - - @Override - public void stateChanged(String state, String title) { - browserNavigation.pushState(state, title); - } - - @Override - public CmsSession getCmsSession() { - CmsSession cmsSession = CmsOsgiUtils.getCmsSession(cmsWebApp.getBundleContext(), getSubject()); - return cmsSession; - } - - @Override - public Object getData(String key) { - if (ui != null) { - return ui.getData(key); - } else { - throw new IllegalStateException("UI is not initialized"); - } - } - - @Override - public void setData(String key, Object value) { - if (ui != null) { - ui.setData(key, value); - } else { - throw new IllegalStateException("UI is not initialized"); - } - } - - /* - * EntryPoint IMPLEMENTATION - */ - - @Override - public int createUI() { - Display display = new Display(); - Shell shell = createShell(display); - shell.setLayout(CmsSwtUtils.noSpaceGridLayout()); - CmsSwtUtils.registerCmsView(shell, this); - createContents(shell); - shell.layout(); -// if (shell.getMaximized()) { -// shell.layout(); -// } else { -//// shell.pack(); -// } - shell.open(); - if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) { - eventLoop: while (!shell.isDisposed()) { - try { - if (!display.readAndDispatch()) { - display.sleep(); - } - } catch (Throwable e) { - if (e instanceof SWTError) { - SWTError swtError = (SWTError) e; - if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) { - log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage()); - continue eventLoop; - } else { - log.error("Unexpected SWT error in event loop, shutting down...", e); - break eventLoop; - } - } else if (e instanceof ThreadDeath) { - throw (ThreadDeath) e; - } else if (e instanceof Error) { - log.error("Unexpected error in event loop, shutting down...", e); - break eventLoop; - } else { - log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage()); - continue eventLoop; - } - } - } - if (!display.isDisposed()) - display.dispose(); - } - return 0; - } - - protected Shell createShell(Display display) { - Shell shell; - if (!multipleShells) { - shell = new Shell(display, SWT.NO_TRIM); - shell.setMaximized(true); - } else { - shell = new Shell(display, SWT.SHELL_TRIM); - shell.setSize(800, 600); - } - return shell; - } - - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java deleted file mode 100644 index 2eff71ee8..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.argeo.cms.web; - -import static org.argeo.cms.osgi.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY; - -import java.util.HashMap; -import java.util.Map; - -import org.argeo.cms.osgi.BundleCmsTheme; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.client.WebClient; -import org.osgi.framework.BundleContext; - -/** Lightweight web app using only RWT and not the whole Eclipse platform. */ -public class MinimalWebApp implements ApplicationConfiguration { - - private BundleCmsTheme theme; - - public void init(BundleContext bundleContext, Map 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 properties) { - - } - - @Override - public void configure(Application application) { - if (theme != null) - WebThemeUtils.apply(application, theme); - - Map properties = new HashMap<>(); - if (theme != null) { - properties.put(WebClient.THEME_ID, theme.getThemeId()); - properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders()); - } else { - properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID); - } - addEntryPoints(application, properties); - - } - - public void setTheme(BundleCmsTheme theme) { - this.theme = theme; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java deleted file mode 100644 index f063117ae..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java +++ /dev/null @@ -1,414 +0,0 @@ -package org.argeo.cms.web; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.Privilege; -import javax.jcr.version.VersionManager; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.LifeCycleUiProvider; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.argeo.cms.ui.util.StyleSheetResourceLoader; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.application.EntryPoint; -import org.eclipse.rap.rwt.application.EntryPointFactory; -import org.eclipse.rap.rwt.application.ExceptionHandler; -import org.eclipse.rap.rwt.client.WebClient; -import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; - -/** A basic generic app based on {@link SimpleErgonomics}. */ -@Deprecated -public class SimpleApp implements CmsUiConstants, ApplicationConfiguration { - private final static CmsLog log = CmsLog.getLog(SimpleApp.class); - - private String contextName = null; - - private Map> branding = new HashMap>(); - private Map> styleSheets = new HashMap>(); - - private List resources = new ArrayList(); - - private BundleContext bundleContext; - - private Repository repository; - private String workspace = null; - private String jcrBasePath = "/"; - private List roPrincipals = Arrays.asList(CmsConstants.ROLE_ANONYMOUS, CmsConstants.ROLE_USER); - private List rwPrincipals = Arrays.asList(CmsConstants.ROLE_USER); - - private CmsUiProvider header; - private Map pages = new LinkedHashMap(); - - private Integer headerHeight = 40; - - private ServiceRegistration 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 defaultBranding = null; - if (branding.containsKey("*")) - defaultBranding = branding.get("*"); - // String defaultTheme = defaultBranding.get(WebClient.THEME_ID); - - // entry points - for (String page : pages.keySet()) { - Map properties = defaultBranding != null ? new HashMap(defaultBranding) - : new HashMap(); - 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 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 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 props = new Hashtable(); - 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 pages) { - this.pages = pages; - } - - public void setJcrBasePath(String basePath) { - this.jcrBasePath = basePath; - } - - public void setRoPrincipals(List roPrincipals) { - this.roPrincipals = roPrincipals; - } - - public void setRwPrincipals(List rwPrincipals) { - this.rwPrincipals = rwPrincipals; - } - - public void setHeaderHeight(Integer headerHeight) { - this.headerHeight = headerHeight; - } - - public void setBranding(Map> branding) { - this.branding = branding; - } - - public void setStyleSheets(Map> styleSheets) { - this.styleSheets = styleSheets; - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - public void setResources(List 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 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 properties; - - public CmsEntryPointFactory(CmsUiProvider page, Repository repository, String workspace, - Map 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 = "" - + "" + ""; -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java deleted file mode 100644 index 26ca3705f..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java +++ /dev/null @@ -1,238 +0,0 @@ -package org.argeo.cms.web; - -import java.util.Map; -import java.util.UUID; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.UxContext; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.SimpleSwtUxContext; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.util.DefaultImageManager; -import org.argeo.cms.ui.util.SystemNotifications; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Simple header/body ergonomics. */ -@Deprecated -public class SimpleErgonomics extends AbstractCmsEntryPoint { - private static final long serialVersionUID = 8743413921359548523L; - - private final static CmsLog log = CmsLog.getLog(SimpleErgonomics.class); - - private boolean uiInitialized = false; - private Composite headerArea; - private Composite leftArea; - private Composite rightArea; - private Composite footerArea; - private Composite bodyArea; - private final CmsUiProvider uiProvider; - - private CmsUiProvider header; - private Integer headerHeight = 0; - private Integer footerHeight = 0; - private CmsUiProvider lead; - private CmsUiProvider end; - private CmsUiProvider footer; - - private CmsImageManager imageManager = new DefaultImageManager(); - private UxContext uxContext = null; - private String uid; - - public SimpleErgonomics(Repository repository, String workspace, String defaultPath, CmsUiProvider uiProvider, - Map factoryProperties) { - super(repository, workspace, defaultPath, factoryProperties); - this.uiProvider = uiProvider; - } - - @Override - protected void initUi(Composite parent) { - uid = UUID.randomUUID().toString(); - parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false))); - - uxContext = new SimpleSwtUxContext(); - if (!getUxContext().isMasterData()) - createAdminArea(parent); - headerArea = new Composite(parent, SWT.NONE); - headerArea.setLayout(new FillLayout()); - GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1); - headerData.heightHint = headerHeight; - headerArea.setLayoutData(headerData); - - // TODO: bi-directional - leftArea = new Composite(parent, SWT.NONE); - leftArea.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false)); - leftArea.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - bodyArea = new Composite(parent, SWT.NONE); - bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY); - bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - // TODO: bi-directional - rightArea = new Composite(parent, SWT.NONE); - rightArea.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false)); - rightArea.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - footerArea = new Composite(parent, SWT.NONE); - // footerArea.setLayout(new FillLayout()); - GridData footerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1); - footerData.heightHint = footerHeight; - footerArea.setLayoutData(footerData); - - uiInitialized = true; - refresh(); - } - - @Override - protected void refresh() { - if (!uiInitialized) - return; - if (getState() == null) - setState(""); - refreshSides(); - refreshBody(); - if (log.isTraceEnabled()) - log.trace("UI refreshed " + getNode()); - } - - protected void createAdminArea(Composite parent) { - } - - @Deprecated - protected void refreshHeader() { - if (header == null) - return; - - for (Control child : headerArea.getChildren()) - child.dispose(); - try { - header.createUi(headerArea, getNode()); - } catch (RepositoryException e) { - throw new CmsException("Cannot refresh header", e); - } - headerArea.layout(true, true); - } - - protected void refreshSides() { - refresh(headerArea, header, CmsStyles.CMS_HEADER); - refresh(leftArea, lead, CmsStyles.CMS_LEAD); - refresh(rightArea, end, CmsStyles.CMS_END); - refresh(footerArea, footer, CmsStyles.CMS_FOOTER); - } - - private void refresh(Composite area, CmsUiProvider uiProvider, String style) { - if (uiProvider == null) - return; - - for (Control child : area.getChildren()) - child.dispose(); - CmsSwtUtils.style(area, style); - try { - uiProvider.createUi(area, getNode()); - } catch (RepositoryException e) { - throw new CmsException("Cannot refresh header", e); - } - area.layout(true, true); - } - - protected void refreshBody() { - // Exception - Throwable exception = getException(); - if (exception != null) { - SystemNotifications systemNotifications = new SystemNotifications(bodyArea); - systemNotifications.notifyException(exception); - resetException(); - return; - // TODO report - } - - // clear - for (Control child : bodyArea.getChildren()) - child.dispose(); - bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - try { - Node node = getNode(); -// if (node == null) -// log.error("Context cannot be null"); -// else - uiProvider.createUi(bodyArea, node); - } catch (RepositoryException e) { - throw new CmsException("Cannot refresh body", e); - } - - bodyArea.layout(true, true); - } - - @Override - public UxContext getUxContext() { - return uxContext; - } - @Override - public String getUid() { - return uid; - } - - public CmsImageManager getImageManager() { - return imageManager; - } - - public void setHeader(CmsUiProvider header) { - this.header = header; - } - - public void setHeaderHeight(Integer headerHeight) { - this.headerHeight = headerHeight; - } - - public void setImageManager(CmsImageManager imageManager) { - this.imageManager = imageManager; - } - - public CmsUiProvider getLead() { - return lead; - } - - public void setLead(CmsUiProvider lead) { - this.lead = lead; - } - - public CmsUiProvider getEnd() { - return end; - } - - public void setEnd(CmsUiProvider end) { - this.end = end; - } - - public CmsUiProvider getFooter() { - return footer; - } - - public void setFooter(CmsUiProvider footer) { - this.footer = footer; - } - - public CmsUiProvider getHeader() { - return header; - } - - public void setFooterHeight(Integer footerHeight) { - this.footerHeight = footerHeight; - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java deleted file mode 100644 index ea2ebdfb4..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.web; - -import org.argeo.api.cms.CmsTheme; -import org.argeo.api.cms.CmsLog; -import org.eclipse.rap.rwt.application.Application; -import org.eclipse.rap.rwt.service.ResourceLoader; - -/** Web specific utilities around theming. */ -public class WebThemeUtils { - private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class); - - public static void apply(Application application, CmsTheme theme) { - ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme); - resources: for (String path : theme.getImagesPaths()) { - if (path.startsWith("target/")) - continue resources; // skip maven output - application.addResource(path, resourceLoader); - if (log.isTraceEnabled()) - log.trace("Theme " + theme.getThemeId() + ": added resource " + path); - } - for (String path : theme.getRapCssPaths()) { - application.addStyleSheet(theme.getThemeId(), path, resourceLoader); - if (log.isDebugEnabled()) - log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path); - } - } - -} diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java deleted file mode 100644 index 29165a4ef..000000000 --- a/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.argeo.eclipse.ui.jetty; - -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.nio.file.Files; -import java.nio.file.Path; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.rap.rwt.application.AbstractEntryPoint; -import org.eclipse.rap.rwt.application.ApplicationRunner; -import org.eclipse.rap.rwt.engine.RWTServlet; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -/** A minimal RWT runner based on embedded Jetty. */ -public class RwtRunner { - - private final Server server; - private final ServerConnector serverConnector; - private Path tempDir; - - public RwtRunner() { - server = new Server(new QueuedThreadPool(10, 1)); - serverConnector = new ServerConnector(server); - serverConnector.setPort(0); - server.setConnectors(new Connector[] { serverConnector }); - } - - protected Control createUi(Composite parent, Object context) { - return new Label(parent, SWT.NONE); - } - - public void init() { - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - server.setHandler(context); - - String entryPoint = "app"; - - // rwt-resources requires a file system - try { - tempDir = Files.createTempDirectory("argeo-rwtRunner"); - context.setBaseResource(Resource.newResource(tempDir.resolve("www").toString())); - } catch (IOException e) { - throw new IllegalStateException("Cannot create temporary directory", e); - } - context.addEventListener(new ServletContextListener() { - ApplicationRunner applicationRunner; - - @Override - public void contextInitialized(ServletContextEvent sce) { - applicationRunner = new ApplicationRunner( - (application) -> application.addEntryPoint("/" + entryPoint, () -> new AbstractEntryPoint() { - private static final long serialVersionUID = 5678385921969090733L; - - @Override - protected void createContents(Composite parent) { - createUi(parent, null); - } - }, null), sce.getServletContext()); - applicationRunner.start(); - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - applicationRunner.stop(); - } - }); - - context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint); - - // Required to serve rwt-resources. It is important that this is last. - ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); - context.addServlet(holderPwd, "/"); - - try { - server.start(); - } catch (Exception e) { - throw new IllegalStateException("Cannot start Jetty server", e); - } - } - - public void destroy() { - try { - serverConnector.close(); - server.stop(); - // TODO delete temp dir - } catch (Exception e) { - e.printStackTrace(); - } - } - - public Integer getEffectivePort() { - return serverConnector.getLocalPort(); - } - - public void waitFor() throws InterruptedException { - server.join(); - } - - public static void main(String[] args) throws Exception { - RwtRunner rwtRunner = new RwtRunner() { - - @Override - protected Control createUi(Composite parent, Object context) { - Label label = new Label(parent, SWT.NONE); - label.setText("Hello world!"); - return label; - } - }; - rwtRunner.init(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> rwtRunner.destroy(), "Jetty shutdown")); - - long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - System.out.println("App available in " + jvmUptime + " ms, on port " + rwtRunner.getEffectivePort()); - - // open browser in app mode - Thread.sleep(2000);// wait for RWT to be ready - Runtime.getRuntime().exec("google-chrome --app=http://localhost:" + rwtRunner.getEffectivePort() + "/app"); - - rwtRunner.waitFor(); - } -} diff --git a/rap/org.argeo.swt.specific.rap/.classpath b/rap/org.argeo.swt.specific.rap/.classpath deleted file mode 100644 index e03d341b1..000000000 --- a/rap/org.argeo.swt.specific.rap/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/rap/org.argeo.swt.specific.rap/.project b/rap/org.argeo.swt.specific.rap/.project deleted file mode 100644 index 53d797685..000000000 --- a/rap/org.argeo.swt.specific.rap/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.swt.specific.rap - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rap/org.argeo.swt.specific.rap/META-INF/.gitignore b/rap/org.argeo.swt.specific.rap/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rap/org.argeo.swt.specific.rap/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rap/org.argeo.swt.specific.rap/bnd.bnd b/rap/org.argeo.swt.specific.rap/bnd.bnd deleted file mode 100644 index bcd9b195f..000000000 --- a/rap/org.argeo.swt.specific.rap/bnd.bnd +++ /dev/null @@ -1,5 +0,0 @@ -Import-Package: org.eclipse.swt,\ -org.eclipse.jface.dialogs,\ -org.eclipse.swt.events,\ -javax.servlet.http;version="[3,5)",\ -* diff --git a/rap/org.argeo.swt.specific.rap/build.properties b/rap/org.argeo.swt.specific.rap/build.properties deleted file mode 100644 index fd806ca05..000000000 --- a/rap/org.argeo.swt.specific.rap/build.properties +++ /dev/null @@ -1,2 +0,0 @@ -source.. = src/ -output.. = bin/ diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java deleted file mode 100644 index 6100c1a83..000000000 --- a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; - -public class CmsFileDialog extends FileDialog { - private static final long serialVersionUID = -7540791204102318801L; - - public CmsFileDialog(Shell parent, int style) { - super(parent, style); - } - - public CmsFileDialog(Shell parent) { - super(parent); - } - -} diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java deleted file mode 100644 index 3f30bde25..000000000 --- a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Composite; - -public class CmsFileUpload extends FileUpload { - private static final long serialVersionUID = 8027963992680980549L; - - public CmsFileUpload(Composite parent, int style) { - super(parent, style); - } - - @Override - public void setText(String text) { - super.setText(text); - } - - @Override - public String getFileName() { - return super.getFileName(); - } - - @Override - public String[] getFileNames() { - return super.getFileNames(); - } - - @Override - public void addSelectionListener(SelectionListener listener) { - super.addSelectionListener(listener); - } - -} diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java deleted file mode 100644 index a89b921cd..000000000 --- a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import org.eclipse.jface.viewers.AbstractTableViewer; -import org.eclipse.jface.viewers.ColumnViewer; -import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.widgets.Widget; - -/** Static utilities to bridge differences between RCP and RAP */ -public class EclipseUiSpecificUtils { - - public static void setStyleData(Widget widget, Object data) { - widget.setData(RWT.CUSTOM_VARIANT, data); - } - - public static Object getStyleData(Widget widget) { - return widget.getData(RWT.CUSTOM_VARIANT); - } - - public static void setMarkupData(Widget widget) { - widget.setData(RWT.MARKUP_ENABLED, true); - } - - public static void setMarkupValidationDisabledData(Widget widget) { - widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE); - } - - /** - * TootlTip support is supported only for {@link AbstractTableViewer} in RAP - */ - public static void enableToolTipSupport(Viewer viewer) { - if (viewer instanceof ColumnViewer) - ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer); - } - - private EclipseUiSpecificUtils() { - } -} diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java deleted file mode 100644 index f9ca81682..000000000 --- a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import java.io.IOException; -import java.io.InputStream; - -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.client.ClientFile; -import org.eclipse.rap.rwt.client.service.ClientFileUploader; -import org.eclipse.rap.rwt.dnd.ClientFileTransfer; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.DropTargetAdapter; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.widgets.Control; - -/** Configures a {@link Control} to receive files drop from the client OS. */ -public class FileDropAdapter { - - public void prepareDropTarget(Control control, DropTarget dropTarget) { - dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() }); - dropTarget.addDropListener(new DropTargetAdapter() { - private static final long serialVersionUID = 5361645765549463168L; - - @Override - public void dropAccept(DropTargetEvent event) { - if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) { - event.detail = DND.DROP_NONE; - } - } - - @Override - public void drop(DropTargetEvent event) { - handleFileDrop(control, event); - } - }); - } - - public void handleFileDrop(Control control, DropTargetEvent event) { - ClientFile[] clientFiles = (ClientFile[]) event.data; - ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class); -// DiskFileUploadReceiver receiver = new DiskFileUploadReceiver(); - FileUploadReceiver receiver = new FileUploadReceiver() { - - @Override - public void receive(InputStream stream, FileDetails details) throws IOException { - control.getDisplay().syncExec(() -> { - try { - processUpload(stream, details.getFileName(), details.getContentType()); - } catch (IOException e) { - throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e); - } - }); - } - }; - FileUploadHandler handler = new FileUploadHandler(receiver); -// handler.setMaxFileSize( sizeLimit ); -// handler.setUploadTimeLimit( timeLimit ); - service.submit(handler.getUploadUrl(), clientFiles); -// for (File file : receiver.getTargetFiles()) { -// paths.add(file.toPath()); -// } - } - - /** Executed in UI thread */ - protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { - - } - -} diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java deleted file mode 100644 index 72e17a22d..000000000 --- a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java +++ /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 getData(String key) { - Display display = getDisplay(); - if (display == null) - return null; - return (T) display.getData(key); - } - - public static void setData(String key, Object value) { - Display display = getDisplay(); - if (display == null) - throw new IllegalStateException("Not display available"); - display.setData(key, value); - } - - private static Display getDisplay() { - return Display.getCurrent(); - } - - private UiContext() { - } - -} diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java deleted file mode 100644 index 4ec451f8a..000000000 --- a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */ -package org.argeo.eclipse.ui.specific; \ No newline at end of file diff --git a/rcp/org.argeo.cms.e4.rcp/.classpath b/rcp/org.argeo.cms.e4.rcp/.classpath deleted file mode 100644 index eca7bdba8..000000000 --- a/rcp/org.argeo.cms.e4.rcp/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/rcp/org.argeo.cms.e4.rcp/.gitignore b/rcp/org.argeo.cms.e4.rcp/.gitignore deleted file mode 100644 index 710cd6893..000000000 --- a/rcp/org.argeo.cms.e4.rcp/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/bin/ -/target/ -/exec diff --git a/rcp/org.argeo.cms.e4.rcp/.project b/rcp/org.argeo.cms.e4.rcp/.project deleted file mode 100644 index 64d561913..000000000 --- a/rcp/org.argeo.cms.e4.rcp/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.e4.rcp - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs b/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 0c68a61dc..000000000 --- a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,7 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs b/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs deleted file mode 100644 index f29e940a0..000000000 --- a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs +++ /dev/null @@ -1,3 +0,0 @@ -eclipse.preferences.version=1 -pluginProject.extensions=false -resolve.requirebundle=false diff --git a/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore b/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi deleted file mode 100644 index 5b250eebf..000000000 --- a/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - editorArea - - - - - - - - - - - - - - diff --git a/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties b/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties deleted file mode 100644 index 0a0da7581..000000000 --- a/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties +++ /dev/null @@ -1,23 +0,0 @@ -argeo.osgi.start.2.node=\ -org.eclipse.equinox.http.servlet,\ -org.eclipse.equinox.metatype,\ -org.eclipse.equinox.cm,\ -org.argeo.init - -argeo.osgi.start.3.node=\ -org.argeo.cms,\ -org.argeo.cms.jcr,\ - -applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi -lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle -clearPersistedState=true -#argeo.cms.desktop.inTray=true - -# Remote node: -#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node - -# Logging -log.org.argeo=DEBUG - -argeo.node.useradmin.uris=os:/// -eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application diff --git a/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/rcp/org.argeo.cms.e4.rcp/bnd.bnd deleted file mode 100644 index ff79c8041..000000000 --- a/rcp/org.argeo.cms.e4.rcp/bnd.bnd +++ /dev/null @@ -1,7 +0,0 @@ -Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true - -Require-Bundle: org.eclipse.core.runtime - -Import-Package: !org.eclipse.core.runtime,\ -org.eclipse.swt,\ -* diff --git a/rcp/org.argeo.cms.e4.rcp/build.properties b/rcp/org.argeo.cms.e4.rcp/build.properties deleted file mode 100644 index 355413e4f..000000000 --- a/rcp/org.argeo.cms.e4.rcp/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - argeo-companion.e4xmi -source.. = src/ diff --git a/rcp/org.argeo.cms.e4.rcp/log4j.properties b/rcp/org.argeo.cms.e4.rcp/log4j.properties deleted file mode 100644 index 13f949ff5..000000000 --- a/rcp/org.argeo.cms.e4.rcp/log4j.properties +++ /dev/null @@ -1,32 +0,0 @@ -log4j.rootLogger=WARN, development - -## Levels -log4j.logger.org.argeo=DEBUG -log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN -log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO - -#log4j.logger.org.springframework.security=DEBUG -#log4j.logger.org.apache.commons.exec=DEBUG -#log4j.logger.org.apache.jackrabbit.webdav=DEBUG -#log4j.logger.org.apache.jackrabbit.remote=DEBUG -#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG - -log4j.logger.org.apache.catalina=INFO -log4j.logger.org.apache.coyote=INFO - -log4j.logger.org.apache.directory=INFO -log4j.logger.org.apache.directory.server=ERROR -log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR - -## Appenders -# console is set to be a ConsoleAppender. -log4j.appender.console=org.apache.log4j.ConsoleAppender - -# console uses PatternLayout. -log4j.appender.console.layout=org.apache.log4j.PatternLayout -log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n - -# development appender (slow!) -log4j.appender.development=org.apache.log4j.ConsoleAppender -log4j.appender.development.layout=org.apache.log4j.PatternLayout -log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n diff --git a/rcp/org.argeo.cms.e4.rcp/plugin.xml b/rcp/org.argeo.cms.e4.rcp/plugin.xml deleted file mode 100644 index 3e6896beb..000000000 --- a/rcp/org.argeo.cms.e4.rcp/plugin.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java deleted file mode 100644 index a708af13f..000000000 --- a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.argeo.cms.e4.rcp; - -import java.security.PrivilegedExceptionAction; -import java.util.UUID; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.UxContext; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.SimpleSwtUxContext; -import org.argeo.cms.swt.auth.CmsLoginShell; -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IExtension; -import org.eclipse.core.runtime.Platform; -import org.eclipse.equinox.app.IApplication; -import org.eclipse.equinox.app.IApplicationContext; -import org.eclipse.swt.widgets.Display; - -public class CmsE4Application implements IApplication, CmsView { - private LoginContext loginContext; - private IApplication e4Application; - private UxContext uxContext; - private String uid; - - @Override - public Object start(IApplicationContext context) throws Exception { - // TODO wait for CMS to be ready - Thread.sleep(5000); - - uid = UUID.randomUUID().toString(); - Subject subject = new Subject(); - Display display = createDisplay(); - CmsLoginShell loginShell = new CmsLoginShell(this, null); - // TODO customize CmsLoginShell to be smaller and centered - loginShell.setSubject(subject); - try { - // try pre-auth - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell); - loginContext.login(); - } catch (LoginException e) { - e.printStackTrace(); - loginShell.createUi(); - loginShell.open(); - - while (!loginShell.getShell().isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - if (CurrentUser.getUsername(getSubject()) == null) - throw new CmsException("Cannot log in"); - - // try { - // CallbackHandler callbackHandler = new DefaultLoginDialog( - // display.getActiveShell()); - // loginContext = new LoginContext( - // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject, - // callbackHandler); - // } catch (LoginException e1) { - // throw new CmsException("Cannot initialize login context", e1); - // } - // - // // login - // try { - // loginContext.login(); - // subject = loginContext.getSubject(); - // } catch (LoginException e) { - // e.printStackTrace(); - // display.dispose(); - // try { - // Thread.sleep(2000); - // } catch (InterruptedException e1) { - // // silent - // } - // return null; - // } - - uxContext = new SimpleSwtUxContext(); - // UiContext.setData(CmsView.KEY, this); - CmsSwtUtils.registerCmsView(loginShell.getShell(), this); - e4Application = getApplication(null); - Object res = Subject.doAs(subject, new PrivilegedExceptionAction() { - - @Override - public Object run() throws Exception { - return e4Application.start(context); - } - - }); - return res; - } - - @Override - public void stop() { - if (e4Application != null) - e4Application.stop(); - } - - static IApplication getApplication(String[] args) { - IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME, - Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application"); - try { - IConfigurationElement[] elements = extension.getConfigurationElements(); - if (elements.length > 0) { - IConfigurationElement[] runs = elements[0].getChildren("run"); - if (runs.length > 0) { - Object runnable; - runnable = runs[0].createExecutableExtension("class"); - if (runnable instanceof IApplication) - return (IApplication) runnable; - } - } - } catch (Exception e) { - throw new IllegalStateException("Cannot find e4 application", e); - } - throw new IllegalStateException("Cannot find e4 application"); - } - - public static Display createDisplay() { - Display.setAppName("Argeo CMS RCP"); - - // create the display - Display newDisplay = Display.getCurrent(); - if (newDisplay == null) { - newDisplay = new Display(); - } - // Set the priority higher than normal so as to be higher - // than the JobManager. - Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1)); - return newDisplay; - } - - // - // CMS VIEW - // - - @Override - public UxContext getUxContext() { - return uxContext; - } - - @Override - public void navigateTo(String state) { - // TODO Auto-generated method stub - - } - - @Override - public void authChange(LoginContext loginContext) { - if (loginContext == null) - throw new CmsException("Login context cannot be null"); - // logout previous login context - // if (this.loginContext != null) - // try { - // this.loginContext.logout(); - // } catch (LoginException e1) { - // System.err.println("Could not log out: " + e1); - // } - this.loginContext = loginContext; - } - - @Override - public void logout() { - if (loginContext == null) - throw new CmsException("Login context should not bet null"); - try { - CurrentUser.logoutCmsSession(loginContext.getSubject()); - loginContext.logout(); - } catch (LoginException e) { - throw new CmsException("Cannot log out", e); - } - } - - @Override - public void exception(Throwable e) { - // TODO Auto-generated method stub - - } - - @Override - public CmsImageManager getImageManager() { - // TODO Auto-generated method stub - return null; - } - - protected Subject getSubject() { - return loginContext.getSubject(); - } - - @Override - public boolean isAnonymous() { - return CurrentUser.isAnonymous(getSubject()); - } - - @Override - public String getUid() { - return uid; - } - -} diff --git a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java b/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java deleted file mode 100644 index 1d38fe718..000000000 --- a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.cms.e4.rcp; - -import org.eclipse.e4.core.contexts.IEclipseContext; -import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; -import org.eclipse.e4.ui.workbench.lifecycle.PreSave; -import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions; -import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals; - -@SuppressWarnings("restriction") -public class CmsRcpLifeCycle { - - @PostContextCreate - void postContextCreate(IEclipseContext workbenchContext) { - } - - @PreSave - void preSave(IEclipseContext workbenchContext) { - } - - @ProcessAdditions - void processAdditions(IEclipseContext workbenchContext) { - } - - @ProcessRemovals - void processRemovals(IEclipseContext workbenchContext) { - } -} diff --git a/rcp/org.argeo.cms.ui.rcp/.classpath b/rcp/org.argeo.cms.ui.rcp/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/rcp/org.argeo.cms.ui.rcp/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/rcp/org.argeo.cms.ui.rcp/.gitignore b/rcp/org.argeo.cms.ui.rcp/.gitignore deleted file mode 100644 index 09e3bc9b2..000000000 --- a/rcp/org.argeo.cms.ui.rcp/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/bin/ -/target/ diff --git a/rcp/org.argeo.cms.ui.rcp/.project b/rcp/org.argeo.cms.ui.rcp/.project deleted file mode 100644 index c9c2a44bf..000000000 --- a/rcp/org.argeo.cms.ui.rcp/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.ui.rcp - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore b/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml b/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml deleted file mode 100644 index 6da9ae8ef..000000000 --- a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/rcp/org.argeo.cms.ui.rcp/bnd.bnd b/rcp/org.argeo.cms.ui.rcp/bnd.bnd deleted file mode 100644 index 72b00d2cd..000000000 --- a/rcp/org.argeo.cms.ui.rcp/bnd.bnd +++ /dev/null @@ -1,9 +0,0 @@ - -Service-Component: OSGI-INF/cmsRcpApp.xml - -Import-Package:\ -org.argeo.cms.auth,\ -org.eclipse.swt,\ -org.eclipse.swt.graphics,\ -org.w3c.css.sac,\ -* diff --git a/rcp/org.argeo.cms.ui.rcp/build.properties b/rcp/org.argeo.cms.ui.rcp/build.properties deleted file mode 100644 index 6210e849b..000000000 --- a/rcp/org.argeo.cms.ui.rcp/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - OSGI-INF/ -source.. = src/ diff --git a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java b/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java deleted file mode 100644 index 8614d70b5..000000000 --- a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java +++ /dev/null @@ -1,276 +0,0 @@ -package org.argeo.cms.ui.rcp; - -import java.io.IOException; -import java.io.InputStream; -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsApp; -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.CmsTheme; -import org.argeo.api.cms.CmsUi; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.UxContext; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.e4.ui.css.core.engine.CSSEngine; -import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler; -import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventAdmin; - -/** Runs a {@link CmsApp} as an SWT desktop application. */ -@SuppressWarnings("restriction") -public class CmsRcpApp implements CmsView { - private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class); - - private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpApp.class).getBundleContext(); - - private Display display; - private Shell shell; - private CmsApp cmsApp; - private CmsUiThread uiThread; - - // CMS View - private String uid; - private LoginContext loginContext; - - private EventAdmin eventAdmin; - - private CSSEngine cssEngine; - - private CmsUi ui; - // TODO make it configurable - private String uiName = "desktop"; - - public CmsRcpApp() { - uid = UUID.randomUUID().toString(); - } - - public void init(Map properties) { - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - // silent - } - uiThread = new CmsUiThread(); - uiThread.start(); - - } - - public void destroy(Map 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) () -> { - - // 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 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 doAs(PrivilegedAction action) { - return Subject.doAs(getSubject(), action); - } - - protected Subject getSubject() { - return loginContext.getSubject(); - } - - /* - * DEPENDENCY INJECTION - */ - public void setCmsApp(CmsApp cmsApp) { - this.cmsApp = cmsApp; - } - - public void setEventAdmin(EventAdmin eventAdmin) { - this.eventAdmin = eventAdmin; - } - -} diff --git a/rcp/org.argeo.swt.minidesktop/.classpath b/rcp/org.argeo.swt.minidesktop/.classpath deleted file mode 100644 index eca7bdba8..000000000 --- a/rcp/org.argeo.swt.minidesktop/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/rcp/org.argeo.swt.minidesktop/.gitignore b/rcp/org.argeo.swt.minidesktop/.gitignore deleted file mode 100644 index 97adb723b..000000000 --- a/rcp/org.argeo.swt.minidesktop/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/bin/ -/target/ -*.log \ No newline at end of file diff --git a/rcp/org.argeo.swt.minidesktop/.project b/rcp/org.argeo.swt.minidesktop/.project deleted file mode 100644 index b6c2c1ae4..000000000 --- a/rcp/org.argeo.swt.minidesktop/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.swt.minidesktop - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore b/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rcp/org.argeo.swt.minidesktop/bnd.bnd b/rcp/org.argeo.swt.minidesktop/bnd.bnd deleted file mode 100644 index f3c13bec5..000000000 --- a/rcp/org.argeo.swt.minidesktop/bnd.bnd +++ /dev/null @@ -1,2 +0,0 @@ -Import-Package: org.eclipse.swt,\ -* diff --git a/rcp/org.argeo.swt.minidesktop/build.properties b/rcp/org.argeo.swt.minidesktop/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/rcp/org.argeo.swt.minidesktop/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java deleted file mode 100644 index 406382bce..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java +++ /dev/null @@ -1,187 +0,0 @@ -package org.argeo.minidesktop; - -import java.util.Arrays; -import java.util.List; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.browser.Browser; -import org.eclipse.swt.browser.LocationAdapter; -import org.eclipse.swt.browser.LocationEvent; -import org.eclipse.swt.events.KeyAdapter; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** A very minimalistic web browser based on {@link Browser}. */ -public class MiniBrowser { - private static Point defaultShellSize = new Point(800, 480); - - private Browser browser; - private Text addressT; - - private final boolean fullscreen; - private final boolean appMode; - - public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) { - this.fullscreen = fullscreen; - this.appMode = appMode; - createUi(composite); - setUrl(url); - } - - public Control createUi(Composite parent) { - parent.setLayout(noSpaceGridLayout(new GridLayout())); - if (!isAppMode()) { - Control toolBar = createToolBar(parent); - toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - } - Control body = createBody(parent); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return body; - } - - protected Control createToolBar(Composite parent) { - Composite toolBar = new Composite(parent, SWT.NONE); - toolBar.setLayout(new FillLayout()); - addressT = new Text(toolBar, SWT.SINGLE); - addressT.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - setUrl(addressT.getText().trim()); - } - }); - return toolBar; - } - - protected Control createBody(Composite parent) { - browser = new Browser(parent, SWT.NONE); - if (isFullScreen()) - browser.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W - browser.getShell().dispose(); - } - } - }); - browser.addLocationListener(new LocationAdapter() { - @Override - public void changed(LocationEvent event) { - System.out.println(event); - if (addressT != null) - addressT.setText(event.location); - } - - }); - browser.addTitleListener(e -> titleChanged(e.title)); - browser.addOpenWindowListener((e) -> { - e.browser = openNewBrowserWindow(); - }); - return browser; - } - - protected Browser openNewBrowserWindow() { - - if (isFullScreen()) { - // TODO manage multiple tabs? - return browser; - } else { - Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM); - MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode()); - newShell.setSize(defaultShellSize); - newShell.open(); - return newMiniBrowser.browser; - } - } - - protected boolean isFullScreen() { - return fullscreen; - } - - void setUrl(String url) { - if (browser != null && url != null && !url.equals(browser.getUrl())) - browser.setUrl(url.toString()); - } - - /** Called when URL changed; to be overridden, does nothing by default. */ - protected void urlChanged(String url) { - } - - /** Called when title changed; to be overridden, does nothing by default. */ - protected void titleChanged(String title) { - } - - protected Browser getBrowser() { - return browser; - } - - protected boolean isAppMode() { - return appMode; - } - - private static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - public static void main(String[] args) { - List options = Arrays.asList(args); - if (options.contains("--help")) { - System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]"); - System.out.println("A minimalistic web browser Eclipse SWT Browser integration."); - System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)"); - System.out.println(" --app : open without an address bar and a toolbar"); - System.out.println(" --help : print this help and exit"); - System.exit(1); - } - boolean fullscreen = options.contains("--fullscreen"); - boolean appMode = options.contains("--app"); - String url = "https://start.duckduckgo.com/"; - if (options.size() > 0) { - String last = options.get(options.size() - 1); - if (!last.startsWith("--")) - url = last.trim(); - } - - Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); - Shell shell; - if (fullscreen) { - shell = new Shell(display, SWT.NO_TRIM); - shell.setFullScreen(true); - Rectangle bounds = display.getBounds(); - shell.setSize(bounds.width, bounds.height); - } else { - shell = new Shell(display, SWT.SHELL_TRIM); - shell.setSize(defaultShellSize); - } - - new MiniBrowser(shell, url, fullscreen, appMode) { - - @Override - protected void titleChanged(String title) { - shell.setText(title); - } - }; - shell.open(); - - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java deleted file mode 100644 index db5e6f273..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.argeo.minidesktop; - -import java.io.IOException; -import java.io.InputStream; - -import org.eclipse.swt.SWTException; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; - -/** Icons. */ -public class MiniDesktopImages { - - public final Image homeIcon; - public final Image exitIcon; - - public final Image terminalIcon; - public final Image browserIcon; - public final Image explorerIcon; - public final Image textEditorIcon; - - public final Image folderIcon; - public final Image fileIcon; - - public MiniDesktopImages(Display display) { - homeIcon = loadImage(display, "nav_home@2x.png"); - exitIcon = loadImage(display, "delete@2x.png"); - - terminalIcon = loadImage(display, "console_view@2x.png"); - browserIcon = loadImage(display, "external_browser@2x.png"); - explorerIcon = loadImage(display, "fldr_obj@2x.png"); - textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png"); - - folderIcon = loadImage(display, "fldr_obj@2x.png"); - fileIcon = loadImage(display, "file_obj@2x.png"); - } - - static Image loadImage(Display display, String path) { - InputStream stream = MiniDesktopImages.class.getResourceAsStream(path); - if (stream == null) - throw new IllegalArgumentException("Image " + path + " not found"); - Image image = null; - try { - image = new Image(display, stream); - } catch (SWTException ex) { - } finally { - try { - stream.close(); - } catch (IOException ex) { - } - } - return image; - } -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java deleted file mode 100644 index e0f483d49..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java +++ /dev/null @@ -1,349 +0,0 @@ -package org.argeo.minidesktop; - -import java.lang.management.ManagementFactory; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.CTabFolder; -import org.eclipse.swt.custom.CTabItem; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.program.Program; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.ToolBar; -import org.eclipse.swt.widgets.ToolItem; - -/** A very minimalistic desktop manager based on Java and Eclipse SWT. */ -public class MiniDesktopManager { - private Display display; - - private Shell rootShell; - private Shell toolBarShell; - private CTabFolder tabFolder; - private int maxTabTitleLength = 16; - - private final boolean fullscreen; - private final boolean stacking; - - private MiniDesktopImages images; - - public MiniDesktopManager(boolean fullscreen, boolean stacking) { - this.fullscreen = fullscreen; - this.stacking = stacking; - } - - public void init() { - Display.setAppName("Mini SWT Desktop"); - display = Display.getCurrent(); - if (display != null) - throw new IllegalStateException("Already a display " + display); - display = new Display(); - - if (display.getTouchEnabled()) { - System.out.println("Touch enabled."); - } - - images = new MiniDesktopImages(display); - - int toolBarSize = 48; - - if (isFullscreen()) { - rootShell = new Shell(display, SWT.NO_TRIM); - rootShell.setFullScreen(true); - Rectangle bounds = display.getBounds(); - rootShell.setLocation(0, 0); - rootShell.setSize(bounds.width, bounds.height); - } else { - rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE); - Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480); - rootShell.setSize(shellArea.width, shellArea.height); - rootShell.setText(Display.getAppName()); - rootShell.setImage(images.terminalIcon); - } - - rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false))); - Composite toolBarArea = new Composite(rootShell, SWT.NONE); - toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y)); - - ToolBar toolBar; - if (isFullscreen()) { - toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP); - toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER); - createDock(toolBar); - toolBarShell.pack(); - toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y)); - } else { - toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER); - createDock(toolBar); - toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); - } - - if (isStacking()) { - tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM); - tabFolder.setLayout(noSpaceGridLayout(new GridLayout())); - tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION); - tabFolder.setSelectionBackground(selectionBackground); - - // background - Control background = createBackground(tabFolder); - CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE); - homeTabItem.setText("Home"); - homeTabItem.setImage(images.homeIcon); - homeTabItem.setControl(background); - tabFolder.setFocus(); - } else { - createBackground(rootShell); - } - - rootShell.open(); - // rootShell.layout(true, true); - - if (toolBarShell != null) { - int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2; - toolBarShell.setLocation(0, toolBarShellY); - toolBarShell.open(); - } - - long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms."); - } - - protected void createDock(ToolBar toolBar) { - // Terminal - addToolItem(toolBar, images.terminalIcon, "Terminal", () -> { - String url = System.getProperty("user.home"); - AppContext appContext = createAppParent(images.terminalIcon); - new MiniTerminal(appContext.getAppParent(), url) { - - @Override - protected void exitCalled() { - if (appContext.shell != null) - appContext.shell.dispose(); - if (appContext.tabItem != null) - appContext.tabItem.dispose(); - } - }; - String title; - try { - title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - title = System.getProperty("user.name") + "@localhost"; - } - if (appContext.shell != null) - appContext.shell.setText(title); - if (appContext.tabItem != null) { - appContext.tabItem.setText(tabTitle(title)); - appContext.tabItem.setToolTipText(title); - } - openApp(appContext); - }); - - // Web browser - addToolItem(toolBar, images.browserIcon, "Browser", () -> { - String url = "https://start.duckduckgo.com/"; - AppContext appContext = createAppParent(images.browserIcon); - new MiniBrowser(appContext.getAppParent(), url, false, false) { - @Override - protected void titleChanged(String title) { - if (appContext.shell != null) - appContext.shell.setText(title); - if (appContext.tabItem != null) { - appContext.tabItem.setText(tabTitle(title)); - appContext.tabItem.setToolTipText(title); - } - } - }; - openApp(appContext); - }); - - // File explorer - addToolItem(toolBar, images.explorerIcon, "Explorer", () -> { - String url = System.getProperty("user.home"); - AppContext appContext = createAppParent(images.explorerIcon); - new MiniExplorer(appContext.getAppParent(), url) { - - @Override - protected void pathChanged(Path path) { - if (appContext.shell != null) - appContext.shell.setText(path.toString()); - if (appContext.tabItem != null) { - appContext.tabItem.setText(path.getFileName().toString()); - appContext.tabItem.setToolTipText(path.toString()); - } - } - }; - openApp(appContext); - }); - - // Separator - new ToolItem(toolBar, SWT.SEPARATOR); - - // Exit - addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose()); - - toolBar.pack(); - } - - protected String tabTitle(String title) { - return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title; - } - - protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) { - ToolItem searchI = new ToolItem(toolBar, SWT.PUSH); - searchI.setImage(icon); - searchI.setToolTipText(name); - searchI.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetSelected(SelectionEvent e) { - action.run(); - } - - }); - } - - protected AppContext createAppParent(Image icon) { - if (isStacking()) { - Composite appParent = new Composite(tabFolder, SWT.CLOSE); - appParent.setLayout(noSpaceGridLayout(new GridLayout())); - CTabItem item = new CTabItem(tabFolder, SWT.CLOSE); - item.setImage(icon); - item.setControl(appParent); - return new AppContext(item); - } else { - Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM) - : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM); - shell.setImage(icon); - return new AppContext(shell); - } - } - - protected void openApp(AppContext appContext) { - if (appContext.shell != null) { - Shell shell = (Shell) appContext.shell; - shell.open(); - shell.setSize(new Point(800, 480)); - } - if (appContext.tabItem != null) { - tabFolder.setFocus(); - tabFolder.setSelection(appContext.tabItem); - } - } - - protected Control createBackground(Composite parent) { - Composite backgroundArea = new Composite(parent, SWT.NONE); - backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - initBackground(backgroundArea); - return backgroundArea; - } - - protected void initBackground(Composite backgroundArea) { - MiniHomePart homePart = new MiniHomePart() { - - @Override - protected void fillAppsToolBar(ToolBar toolBar) { - createDock(toolBar); - } - }; - homePart.createUiPart(backgroundArea, null); - } - - public void run() { - while (!rootShell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - - public void dispose() { - if (!rootShell.isDisposed()) - rootShell.dispose(); - } - - protected boolean isFullscreen() { - return fullscreen; - } - - protected boolean isStacking() { - return stacking; - } - - protected Image getIconForExt(String ext) { - Program program = Program.findProgram(ext); - if (program == null) - return display.getSystemImage(SWT.ICON_INFORMATION); - - ImageData iconData = program.getImageData(); - if (iconData == null) { - return display.getSystemImage(SWT.ICON_INFORMATION); - } else { - return new Image(display, iconData); - } - - } - - private static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - public static void main(String[] args) { - List options = Arrays.asList(args); - if (options.contains("--help")) { - System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]"); - System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT."); - System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)"); - System.out.println(" --stacking : open apps as tabs (default is to create new windows)"); - System.out.println(" --help : print this help and exit"); - System.exit(1); - } - boolean fullscreen = options.contains("--fullscreen"); - boolean stacking = options.contains("--stacking"); - - MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking); - desktopManager.init(); - desktopManager.run(); - desktopManager.dispose(); - System.exit(0); - } - - class AppContext { - private Shell shell; - private CTabItem tabItem; - - public AppContext(Shell shell) { - this.shell = shell; - } - - public AppContext(CTabItem tabItem) { - this.tabItem = tabItem; - } - - Composite getAppParent() { - if (shell != null) - return shell; - if (tabItem != null) - return (Composite) tabItem.getControl(); - throw new IllegalStateException(); - } - } -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java deleted file mode 100644 index 1395c02cf..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.argeo.minidesktop; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.program.Program; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableItem; -import org.eclipse.swt.widgets.Text; - -public class MiniExplorer { - private Path path; - private Text addressT; - private Table browser; - - private boolean showHidden = false; - - public MiniExplorer(Composite parent, String url) { - this(parent); - setUrl(url); - } - - public MiniExplorer(Composite parent) { - parent.setLayout(noSpaceGridLayout(new GridLayout())); - - Composite toolBar = new Composite(parent, SWT.NONE); - toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - toolBar.setLayout(new FillLayout()); - addressT = new Text(toolBar, SWT.SINGLE); - addressT.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - setUrl(addressT.getText().trim()); - } - }); - browser = createTable(parent, this.path); - - } - - public void setPath(Path url) { - this.path = url; - if (addressT != null) - addressT.setText(url.toString()); - if (browser != null) { - Composite parent = browser.getParent(); - browser.dispose(); - browser = createTable(parent, this.path); - parent.layout(true, true); - } - pathChanged(url); - } - - protected void pathChanged(Path path) { - - } - - protected Table createTable(Composite parent, Path path) { - Table table = new Table(parent, SWT.BORDER); - table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - table.addMouseListener(new MouseAdapter() { - - @Override - public void mouseDoubleClick(MouseEvent e) { - Point pt = new Point(e.x, e.y); - TableItem item = table.getItem(pt); - Path path = (Path) item.getData(); - if (Files.isDirectory(path)) { - setPath(path); - } else { - Program.launch(path.toString()); - } - } - }); - - if (path != null) { - if (path.getParent() != null) { - TableItem parentTI = new TableItem(table, SWT.NONE); - parentTI.setText(".."); - parentTI.setData(path.getParent()); - } - - try { - // directories - DirectoryStream ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p)); - ds.forEach(p -> { - TableItem ti = new TableItem(table, SWT.NONE); - ti.setText(p.getFileName().toString() + "/"); - ti.setData(p); - }); - // files - ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p)); - ds.forEach(p -> { - TableItem ti = new TableItem(table, SWT.NONE); - ti.setText(p.getFileName().toString()); - ti.setData(p); - }); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - } - return table; - } - - protected boolean isShown(Path path) { - if (showHidden) - return true; - try { - return !Files.isHidden(path); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot check " + path, e); - } - } - - public void setUrl(String url) { - setPath(Paths.get(url)); - } - - private static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - public static void main(String[] args) { - Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); - Shell shell = new Shell(display, SWT.SHELL_TRIM); - - String url = args.length > 0 ? args[0] : System.getProperty("user.home"); - new MiniExplorer(shell, url) { - - @Override - protected void pathChanged(Path path) { - shell.setText(path.toString()); - } - - }; - - shell.open(); - shell.setSize(new Point(800, 480)); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java deleted file mode 100644 index 877f64384..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.argeo.minidesktop; - -import java.net.InetAddress; -import java.net.InterfaceAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Enumeration; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.ProgressBar; -import org.eclipse.swt.widgets.ToolBar; - -/** A start page displaying network information and resources. */ -public class MiniHomePart { - - public Control createUiPart(Composite parent, Object context) { - parent.setLayout(new GridLayout(2, false)); - Display display = parent.getDisplay(); - - // Apps - Group appsGroup = new Group(parent, SWT.NONE); - appsGroup.setText("Apps"); - appsGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 2, 1)); - ToolBar appsToolBar = new ToolBar(appsGroup, SWT.HORIZONTAL | SWT.FLAT); - fillAppsToolBar(appsToolBar); - - // Host - Group hostGroup = new Group(parent, SWT.NONE); - hostGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); - hostGroup.setText("Host"); - hostGroup.setLayout(new GridLayout(2, false)); - label(hostGroup, "Hostname: "); - try { - InetAddress defaultAddr = InetAddress.getLocalHost(); - String hostname = defaultAddr.getHostName(); - label(hostGroup, hostname); - label(hostGroup, "Address: "); - label(hostGroup, defaultAddr.getHostAddress()); - } catch (UnknownHostException e) { - label(hostGroup, e.getMessage()); - } - - Enumeration netInterfaces = null; - try { - netInterfaces = NetworkInterface.getNetworkInterfaces(); - } catch (SocketException e) { - label(hostGroup, "Interfaces: "); - label(hostGroup, e.getMessage()); - } - if (netInterfaces != null) - while (netInterfaces.hasMoreElements()) { - NetworkInterface netInterface = netInterfaces.nextElement(); - byte[] hardwareAddress = null; - try { - hardwareAddress = netInterface.getHardwareAddress(); - if (hardwareAddress != null) { - label(hostGroup, convertHardwareAddress(hardwareAddress)); - label(hostGroup, netInterface.getName()); - for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) { - label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress())); - label(hostGroup, Short.toString(addr.getNetworkPrefixLength())); - } - } - } catch (SocketException e) { - label(hostGroup, e.getMessage()); - } - } - - // Resources - Group resGroup = new Group(parent, SWT.NONE); - resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - resGroup.setText("Resources"); - resGroup.setLayout(new GridLayout(3, false)); - - Runtime runtime = Runtime.getRuntime(); - - String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB"; - label(resGroup, "Max Java memory: "); - label(resGroup, maxMemoryStr); - label(resGroup, "Java version: " + Runtime.version().toString()); - - label(resGroup, "Usable Java memory: "); - Label totalMemory = label(resGroup, maxMemoryStr); - ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH); - totalOnMax.setMaximum(100); - label(resGroup, "Used Java memory: "); - Label usedMemory = label(resGroup, maxMemoryStr); - ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH); - totalOnMax.setMaximum(100); - new Thread() { - @Override - public void run() { - while (!totalOnMax.isDisposed()) { - display.asyncExec(() -> { - if (totalOnMax.isDisposed()) - return; - totalOnMax.setSelection(javaTotalOnMaxPerct(runtime)); - usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime)); - totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB"); - usedMemory.setText( - Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB"); - }); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - return; - } - } - } - }.start(); - return parent; - } - - protected void fillAppsToolBar(ToolBar toolBar) { - - } - - protected int javaUsedOnTotalPerct(Runtime runtime) { - return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory()); - } - - protected int javaTotalOnMaxPerct(Runtime runtime) { - return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory()); - } - - protected Label label(Composite parent, String text) { - Label label = new Label(parent, SWT.WRAP); - label.setText(text); - return label; - } - - protected String cleanHostAddress(String hostAddress) { - // remove % from Ipv6 addresses - int index = hostAddress.indexOf('%'); - if (index > 0) - return hostAddress.substring(0, index); - else - return hostAddress; - } - - protected String convertHardwareAddress(byte[] hardwareAddress) { - if (hardwareAddress == null) - return ""; - // from https://stackoverflow.com/a/2797498/7878010 - StringBuilder sb = new StringBuilder(18); - for (byte b : hardwareAddress) { - if (sb.length() > 0) - sb.append(':'); - sb.append(String.format("%02x", b).toUpperCase()); - } - return sb.toString(); - } -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java deleted file mode 100644 index 86ff53fee..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.argeo.minidesktop; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.PaintEvent; -import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.ImageLoader; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Canvas; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; - -public class MiniImageViewer implements PaintListener { - private URL url; - private Canvas area; - - private Image image; - - public MiniImageViewer(Composite parent, int style) { - parent.setLayout(new GridLayout()); - - Composite toolBar = new Composite(parent, SWT.NONE); - toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - toolBar.setLayout(new RowLayout()); - Button load = new Button(toolBar, SWT.FLAT); - load.setText("\u2191");// up arrow - load.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetSelected(SelectionEvent e) { - FileDialog fileDialog = new FileDialog(area.getShell()); - String path = fileDialog.open(); - if (path != null) { - setUrl(path); - } - } - - }); - - area = new Canvas(parent, SWT.NONE); - area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - area.addPaintListener(this); - } - - protected void load(URL url) { - try { - ImageLoader imageLoader = new ImageLoader(); - ImageData[] data = imageLoader.load(url.openStream()); - image = new Image(area.getDisplay(), data[0]); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Override - public void paintControl(PaintEvent e) { - e.gc.drawImage(image, 0, 0); - - } - - protected Path url2path(URL url) { - try { - Path path = Paths.get(url.toURI()); - return path; - } catch (URISyntaxException e) { - throw new IllegalStateException("Cannot convert " + url + " to uri", e); - } - } - - public void setUrl(URL url) { - this.url = url; - if (area != null) - load(this.url); - } - - public void setUrl(String url) { - try { - setUrl(new URL(url)); - } catch (MalformedURLException e) { - // try with http - try { - setUrl(new URL("file://" + url)); - return; - } catch (MalformedURLException e1) { - // nevermind... - } - throw new IllegalArgumentException("Cannot interpret URL " + url, e); - } - } - - public static void main(String[] args) { - Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); - Shell shell = new Shell(display, SWT.SHELL_TRIM); - - MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE); - String url = args.length > 0 ? args[0] : ""; - if (!url.trim().equals("")) { - miniBrowser.setUrl(url); - shell.setText(url); - } else { - shell.setText("*"); - } - - shell.open(); - shell.setSize(new Point(800, 480)); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java deleted file mode 100644 index 196ad0c57..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.argeo.minidesktop; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.StringTokenizer; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.PaintEvent; -import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Canvas; -import org.eclipse.swt.widgets.Caret; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -public class MiniTerminal implements KeyListener, PaintListener { - - private Canvas area; - private Caret caret; - - private StringBuffer buf = new StringBuffer(""); - private StringBuffer userInput = new StringBuffer(""); - private List history = new ArrayList<>(); - - private Point charExtent = null; - private int charsPerLine = 0; - private String[] lines = new String[0]; - private List 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 args = new ArrayList<>(); - while (st.hasMoreTokens()) - args.add(st.nextToken()); - if (args.size() == 0) { - displayPrompt(); - return; - } - - // change directory - if (args.get(0).equals("cd")) { - if (args.size() == 1) { - setPath(homeDir); - } else { - Path newPath = currentDir.resolve(args.get(1)); - if (!Files.exists(newPath) || !Files.isDirectory(newPath)) { - println(newPath + ": No such file or directory"); - return; - } - setPath(newPath); - } - displayPrompt(); - return; - } - // show current directory - else if (args.get(0).equals("pwd")) { - println(currentDir); - displayPrompt(); - return; - } - // exit - else if (args.get(0).equals("exit")) { - println("logout"); - exitCalled(); - return; - } - - ProcessBuilder pb = new ProcessBuilder(args); - pb.redirectErrorStream(true); - pb.directory(currentDir.toFile()); -// Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile()); - process = pb.start(); - - stdIn = process.getOutputStream(); - readOut = new Thread("MiniTerminal read out") { - @Override - public void run() { - running = true; - try (BufferedReader in = new BufferedReader( - new InputStreamReader(process.getInputStream(), charset))) { - String line = null; - while ((line = in.readLine()) != null) { - println(line); - } - } catch (IOException e) { - println(e.getMessage()); - } - stdIn = null; - displayPrompt(); - running = false; - readOut = null; - process = null; - } - }; - readOut.start(); - } catch (IOException e) { - println(e.getMessage()); - displayPrompt(); - } - } - - protected int linesForLogicalLine(char[] line) { - return line.length / charsPerLine + 1; - } - - protected void println(Object line) { - buf.append(line); - markLogicalLine(); - } - - protected void refreshLines(int charPerLine, int nbrOfLines) { - if (lines.length != nbrOfLines) { - lines = new String[nbrOfLines]; - Arrays.fill(lines, null); - } - if (this.charsPerLine != charPerLine) - this.charsPerLine = charPerLine; - - int currentLine = nbrOfLines - 1; - // current line - if (buf.length() > 0) { - lines[currentLine] = buf.toString(); - } else { - lines[currentLine] = ""; - } - currentLine--; - - logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) { - char[] logicalLine = logicalLines.get(i).toCharArray(); - int linesNeeded = linesForLogicalLine(logicalLine); - for (int j = linesNeeded - 1; j >= 0; j--) { - int from = j * charPerLine; - int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length); - lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to)); -// System.out.println("Set line " + currentLine + " to : " + lines[currentLine]); - currentLine--; - if (currentLine < 0) - break logicalLines; - } - } - } - - @Override - public void paintControl(PaintEvent e) { - GC gc = e.gc; - gc.setFont(mono); - if (charExtent == null) - charExtent = gc.textExtent("a"); - - Point areaSize = area.getSize(); - int charPerLine = areaSize.x / charExtent.x; - int nbrOfLines = areaSize.y / charExtent.y; - refreshLines(charPerLine, nbrOfLines); - - for (int i = 0; i < lines.length; i++) { - String line = lines[i]; - if (line != null) - gc.drawString(line, 0, i * charExtent.y); - } -// String toDraw = buf.toString(); -// gc.drawString(toDraw, 0, 0); -// area.setCaret(caret); - } - - protected void exitCalled() { - - } - - public void setPath(String path) { - this.currentDir = Paths.get(path); - } - - public void setPath(Path path) { - this.currentDir = path; - } - - public static void main(String[] args) { - Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); - Shell shell = new Shell(display, SWT.SHELL_TRIM); - - String url = args.length > 0 ? args[0] : System.getProperty("user.home"); - new MiniTerminal(shell, url) { - - @Override - protected void exitCalled() { - shell.dispose(); - System.exit(0); - } - }; - - shell.open(); - shell.setSize(new Point(800, 480)); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java deleted file mode 100644 index 91cd19e90..000000000 --- a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.argeo.minidesktop; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -public class MiniTextEditor { - private URL url; - private Text text; - - public MiniTextEditor(Composite parent, int style) { - parent.setLayout(new GridLayout()); - - Composite toolBar = new Composite(parent, SWT.NONE); - toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - toolBar.setLayout(new RowLayout()); - Button load = new Button(toolBar, SWT.FLAT); - load.setText("\u2191");// up arrow - load.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetSelected(SelectionEvent e) { - FileDialog fileDialog = new FileDialog(text.getShell()); - String path = fileDialog.open(); - if (path != null) { - setUrl(path); - } - } - - }); - - Button save = new Button(toolBar, SWT.FLAT); - save.setText("\u2193");// down arrow - // save.setText("\u1F609");// emoji - save.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetSelected(SelectionEvent e) { - save(url); - } - - }); - - text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - } - - protected void load(URL url) { - text.setText(""); - // TODO deal with encoding and binary data - try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { - String line = null; - while ((line = in.readLine()) != null) { - text.append(line + "\n"); - } - text.setEditable(true); - } catch (IOException e) { - if (e instanceof FileNotFoundException) { - Path path = url2path(url); - try { - Files.createFile(path); - load(url); - return; - } catch (IOException e1) { - e = e1; - } - } - text.setText(e.getMessage()); - text.setEditable(false); - e.printStackTrace(); - // throw new IllegalStateException("Cannot load " + url, e); - } - } - - protected Path url2path(URL url) { - try { - Path path = Paths.get(url.toURI()); - return path; - } catch (URISyntaxException e) { - throw new IllegalStateException("Cannot convert " + url + " to uri", e); - } - } - - protected void save(URL url) { - if (!url.getProtocol().equals("file")) - throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write"); - Path path = url2path(url); - try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) { - out.write(text.getText()); - } catch (IOException e) { - throw new IllegalStateException("Cannot save " + url + " to " + path, e); - } - } - - public void setUrl(URL url) { - this.url = url; - if (text != null) - load(url); - } - - public void setUrl(String url) { - try { - setUrl(new URL(url)); - } catch (MalformedURLException e) { - // try with http - try { - setUrl(new URL("file://" + url)); - return; - } catch (MalformedURLException e1) { - // nevermind... - } - throw new IllegalArgumentException("Cannot interpret URL " + url, e); - } - } - - public static void main(String[] args) { - Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); - Shell shell = new Shell(display, SWT.SHELL_TRIM); - - MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE); - String url = args.length > 0 ? args[0] : ""; - if (!url.trim().equals("")) { - miniBrowser.setUrl(url); - shell.setText(url); - } else { - shell.setText("*"); - } - - shell.open(); - shell.setSize(new Point(800, 480)); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - } - -} diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png deleted file mode 100644 index 55c614dfa..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png deleted file mode 100644 index 54ecae20f..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png deleted file mode 100644 index eb2fc720b..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png deleted file mode 100644 index 06d337a43..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png deleted file mode 100644 index 31e671c46..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png deleted file mode 100644 index adb7c2cc2..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png deleted file mode 100644 index 4c9a16c0f..000000000 Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png and /dev/null differ diff --git a/rcp/org.argeo.swt.specific.rcp/.classpath b/rcp/org.argeo.swt.specific.rcp/.classpath deleted file mode 100644 index 457b11571..000000000 --- a/rcp/org.argeo.swt.specific.rcp/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/rcp/org.argeo.swt.specific.rcp/.gitignore b/rcp/org.argeo.swt.specific.rcp/.gitignore deleted file mode 100644 index 5e77890b2..000000000 --- a/rcp/org.argeo.swt.specific.rcp/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -/bin/ -*.log \ No newline at end of file diff --git a/rcp/org.argeo.swt.specific.rcp/.project b/rcp/org.argeo.swt.specific.rcp/.project deleted file mode 100644 index c79ee3fc9..000000000 --- a/rcp/org.argeo.swt.specific.rcp/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.swt.specific.rcp - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore b/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/rcp/org.argeo.swt.specific.rcp/bnd.bnd b/rcp/org.argeo.swt.specific.rcp/bnd.bnd deleted file mode 100644 index bb88efda7..000000000 --- a/rcp/org.argeo.swt.specific.rcp/bnd.bnd +++ /dev/null @@ -1,20 +0,0 @@ -Import-Package: \ -!java.*,\ -org.apache.commons.io,\ -org.eclipse.core.commands,\ -!org.eclipse.core.runtime,\ -!org.eclipse.ui.plugin,\ -org.eclipse.swt,\ -javax.servlet.http;version="[3,5)",\ -javax.servlet;version="[3,5)",\ -* - -Export-Package: org.argeo.*,\ -org.eclipse.rap.fileupload.*;version="3.10",\ -org.eclipse.rap.rwt.*;version="3.10" - -# Was !org.eclipse.core.commands,\ why ? - -#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin -#Bundle-ActivationPolicy: lazy -#Ignore-Package: org.eclipse.core.commands \ No newline at end of file diff --git a/rcp/org.argeo.swt.specific.rcp/build.properties b/rcp/org.argeo.swt.specific.rcp/build.properties deleted file mode 100644 index c6b651a59..000000000 --- a/rcp/org.argeo.swt.specific.rcp/build.properties +++ /dev/null @@ -1,3 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/ diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java deleted file mode 100644 index 0d9ce481d..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.eclipse.ui.rcp.internal.rwt; - -import org.eclipse.rap.rwt.client.Client; -import org.eclipse.rap.rwt.client.service.BrowserNavigation; -import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; -import org.eclipse.rap.rwt.client.service.ClientService; -import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; - -public class RcpClient implements Client { - - @Override - public T getService(Class type) { - if (type.isAssignableFrom(JavaScriptExecutor.class)) - return (T) javaScriptExecutor; - else if (type.isAssignableFrom(BrowserNavigation.class)) - return (T) browserNavigation; - else - return null; - } - - private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() { - - @Override - public void execute(String code) { - // TODO Auto-generated method stub - - } - }; - private BrowserNavigation browserNavigation = new BrowserNavigation() { - - @Override - public void pushState(String state, String title) { - // TODO Auto-generated method stub - - } - - @Override - public void addBrowserNavigationListener( - BrowserNavigationListener listener) { - // TODO Auto-generated method stub - - } - }; -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java deleted file mode 100644 index 91109a9de..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.eclipse.ui.rcp.internal.rwt; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - -import org.apache.commons.io.IOUtils; -import org.eclipse.rap.rwt.service.ResourceManager; - -public class RcpResourceManager implements ResourceManager { - private Map register = Collections - .synchronizedMap(new TreeMap()); - - @Override - public void register(String name, InputStream in) { - try { - register.put(name, IOUtils.toByteArray(in)); - } catch (IOException e) { - throw new RuntimeException("Cannot register " + name, e); - } - } - - @Override - public boolean unregister(String name) { - return register.remove(name) != null; - } - - @Override - public InputStream getRegisteredContent(String name) { - return new ByteArrayInputStream(register.get(name)); - } - - @Override - public String getLocation(String name) { - return name; - } - - @Override - public boolean isRegistered(String name) { - return register.containsKey(name); - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java deleted file mode 100644 index 0c5d34699..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; - -public class CmsFileDialog extends FileDialog { - public CmsFileDialog(Shell parent, int style) { - super(parent, style); - } - - public CmsFileDialog(Shell parent) { - super(parent); - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java deleted file mode 100644 index 638859a85..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Composite; - -public class CmsFileUpload extends FileUpload { - public CmsFileUpload(Composite parent, int style) { - super(parent, style); - } - - @Override - public void setText(String text) { - super.setText(text); - } - - @Override - public String getFileName() { - return super.getFileName(); - } - - @Override - public String[] getFileNames() { - return super.getFileNames(); - } - - @Override - public void addSelectionListener(SelectionListener listener) { - super.addSelectionListener(listener); - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java deleted file mode 100644 index fbb4fbf83..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -/** RCP specific {@link NLS} to be extended */ -public class DefaultNLS {// extends NLS { -// public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin"; -// -// public DefaultNLS() { -// this(DEFAULT_BUNDLE_LOCATION); -// } -// -// public DefaultNLS(String bundleName) { -// NLS.initializeMessages(bundleName, getClass()); -// } -} \ No newline at end of file diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java deleted file mode 100644 index ac862d794..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -/** Constants which are specific to RWT.*/ -public interface EclipseUiConstants { - final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName"; - final static String MARKUP_SUPPORT = null; -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java deleted file mode 100644 index d1acbcfc0..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import org.eclipse.jface.viewers.ColumnViewer; -import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.widgets.Widget; - -/** Static utilities to bridge differences between RCP and RAP */ -public class EclipseUiSpecificUtils { - private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName"; - - public static void setStyleData(Widget widget, Object data) { - widget.setData(CSS_CLASS, data); - } - - public static Object getStyleData(Widget widget) { - return widget.getData(CSS_CLASS); - } - - public static void setMarkupData(Widget widget) { - // does nothing - } - - public static void setMarkupValidationDisabledData(Widget widget) { - // does nothing - } - - /** - * TootlTip support is supported for {@link ColumnViewer} in RCP - * - * @see ColumnViewerToolTipSupport#enableFor(Viewer) - */ - public static void enableToolTipSupport(Viewer viewer) { - if (viewer instanceof ColumnViewer) - ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer); - } - - private EclipseUiSpecificUtils() { - } -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java deleted file mode 100644 index 524447ed0..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; - -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.DropTargetAdapter; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.FileTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.widgets.Control; - -public class FileDropAdapter { - - public void prepareDropTarget(Control control, DropTarget dropTarget) { - dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() }); - dropTarget.addDropListener(new DropTargetAdapter() { - @Override - public void dropAccept(DropTargetEvent event) { - if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) { - event.detail = DND.DROP_NONE; - } - } - - @Override - public void drop(DropTargetEvent event) { - handleFileDrop(control, event); - } - }); - } - - public void handleFileDrop(Control control, DropTargetEvent event) { - String fileList[] = null; - FileTransfer ft = FileTransfer.getInstance(); - if (ft.isSupportedType(event.currentDataType)) { - fileList = (String[]) event.data; - } - System.out.println(Arrays.toString(fileList)); - } - - /** Executed in UI thread */ - protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { - - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java deleted file mode 100644 index 20163cffa..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.argeo.eclipse.ui.specific; - -import java.util.Locale; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.swt.widgets.Display; - -/** Singleton class providing single sources infos about the UI context. */ -public class UiContext { - - public static HttpServletRequest getHttpRequest() { - return null; - } - - public static HttpServletResponse getHttpResponse() { - return null; - } - - public static Locale getLocale() { - return Locale.getDefault(); - } - - public static void setLocale(Locale locale) { - Locale.setDefault(locale); - } - - /** Can always be null */ - @SuppressWarnings("unchecked") - public static T getData(String key) { - Display display = getDisplay(); - if (display == null) - return null; - return (T) display.getData(key); - } - - public static void setData(String key, Object value) { - Display display = getDisplay(); - if (display == null) - throw new IllegalStateException("Not display available"); - display.setData(key, value); - } - - private static Display getDisplay() { - return Display.getCurrent(); - } - - private UiContext() { - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java deleted file mode 100644 index fbb36ddd4..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.eclipse.rap.fileupload; - -public interface FileDetails { - String getContentType(); - - long getContentLength(); - - String getFileName(); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java deleted file mode 100644 index a7452806a..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.eclipse.rap.fileupload; - -import java.util.EventObject; - -public abstract class FileUploadEvent extends EventObject { - - private static final long serialVersionUID = 1L; - - protected FileUploadEvent(FileUploadHandler source) { - super(source); - } - - public abstract FileDetails[] getFileDetails(); - - public abstract long getContentLength(); - - public abstract long getBytesRead(); - - public abstract Exception getException(); - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java deleted file mode 100644 index 7d89300f3..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2011, 2012 EclipseSource and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * EclipseSource - initial API and implementation - ******************************************************************************/ -package org.eclipse.rap.fileupload; - -/** - * A file upload handler is used to accept file uploads from a client. After - * creating a file upload handler, the server will accept file uploads to the - * URL returned by getUploadUrl(). Upload listeners can be attached - * to react on progress. When the upload has finished, a FileUploadHandler has - * to be disposed of by calling its dispose() method. - * - * @noextend This class is not intended to be subclassed by clients. - */ -public class FileUploadHandler { - - public FileUploadHandler(FileUploadReceiver fileUploadReceiver) { - } - - public void dispose() { - - } - - public void addUploadListener(FileUploadListener listener) { - - } - - public String getUploadUrl() { - return null; - } -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java deleted file mode 100644 index b59fd39ea..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2011, 2012 EclipseSource and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * EclipseSource - initial API and implementation - ******************************************************************************/ -package org.eclipse.rap.fileupload; - -import org.eclipse.swt.widgets.Display; - - -/** - * Listener to react on progress and completion of a file upload. - *

    - * Note: This listener will be called from a different thread than the UI thread. - * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI. - *

    - * - * @see FileUploadEvent - */ -public interface FileUploadListener { - - /** - * Called when new information about an in-progress upload is available. - * - * @param event event object that contains information about the uploaded file - * @see FileUploadEvent#getBytesRead() - */ - void uploadProgress( FileUploadEvent event ); - - /** - * Called when a file upload has finished successfully. - * - * @param event event object that contains information about the uploaded file - * @see FileUploadEvent - */ - void uploadFinished( FileUploadEvent event ); - - /** - * Called when a file upload failed. - * - * @param event event object that contains information about the uploaded file - * @see FileUploadEvent#getErrorMessage() - */ - void uploadFailed( FileUploadEvent event ); - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java deleted file mode 100644 index 3f4cf47c4..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2011, 2013 EclipseSource and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * EclipseSource - initial API and implementation - ******************************************************************************/ -package org.eclipse.rap.fileupload; - -import java.io.IOException; -import java.io.InputStream; - - -/** - * Instances of this interface are responsible for reading and processing the data from a file - * upload. - */ -public abstract class FileUploadReceiver { - - /** - * Reads and processes all data from the provided input stream. - * - * @param stream the stream to read from - * @param details the details of the uploaded file like file name, content-type and size - * @throws IOException if an input / output error occurs - */ - public abstract void receive( InputStream stream, FileDetails details ) throws IOException; - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java deleted file mode 100644 index 1688594bb..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.eclipse.rap.rwt; - -import java.util.Locale; - -import javax.servlet.http.HttpServletRequest; - -import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient; -import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager; -import org.eclipse.rap.rwt.client.Client; -import org.eclipse.rap.rwt.service.ResourceManager; - -public class RWT { - public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT"; - public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED"; - public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED"; - public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT"; - public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS"; - public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS"; - public final static String DEFAULT_THEME_ID = "argeo-rcp:DEFAULT_THEME_ID"; - - public final static int HYPERLINK = 0; - - private static Locale locale = Locale.getDefault(); - private static RcpClient client = new RcpClient(); - private static ResourceManager resourceManager = new RcpResourceManager(); - static { - - } - - public static Locale getLocale() { - return locale; - } - - public static HttpServletRequest getRequest() { - return null; - } - - public static ResourceManager getResourceManager() { - return resourceManager; - } - - public static Client getClient() { - return client; - } -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java deleted file mode 100644 index 6e30aa635..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.eclipse.rap.rwt; - -public class SingletonUtil { - public static T getSessionInstance(Class clss) { - return null; - } -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java deleted file mode 100644 index 980a81854..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.eclipse.rap.rwt.application; - -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -public abstract class AbstractEntryPoint implements EntryPoint { - private Display display; - private Shell shell; - - protected Shell createShell(Display display) { - return new Shell(display); - } - - protected void createContents(Composite parent) { - - } - - public int createUI() { - display = new Display(); - shell = createShell(display); - shell.setLayout(new GridLayout(1, false)); - createContents(shell); - if (shell.getMaximized()) { - shell.layout(); - } else { - shell.pack(); - } - shell.open(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - display.dispose(); - return 0; - } - - protected Shell getShell() { - return shell; - } -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java deleted file mode 100644 index 6cb5f29d2..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.eclipse.rap.rwt.application; - -import java.util.Map; - -import org.eclipse.rap.rwt.service.ResourceLoader; - -public interface Application { - public static enum OperationMode { - JEE_COMPATIBILITY, SWT_COMPATIBILITY, - } - - void setOperationMode(OperationMode operationMode); - - void addResource(String name, ResourceLoader resourceLoader); - - void setExceptionHandler(ExceptionHandler exceptionHandler); - - void addEntryPoint(String path, EntryPointFactory entryPointFactory, - Map properties); - - void addEntryPoint(String path, Class entryPoint, - Map properties); - - void addStyleSheet(String themeId, String styleSheetLocation, - ResourceLoader resourceLoader); - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java deleted file mode 100644 index 961ad70f6..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.eclipse.rap.rwt.application; - -public interface ApplicationConfiguration { - void configure(Application application); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java deleted file mode 100644 index c0d559a2b..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.eclipse.rap.rwt.application; - -public interface EntryPoint { - int createUI(); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java deleted file mode 100644 index d5b24d8fe..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.eclipse.rap.rwt.application; - -public interface EntryPointFactory { - public EntryPoint create(); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java deleted file mode 100644 index 13daf2195..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.eclipse.rap.rwt.application; - -public interface ExceptionHandler { - public void handleException(Throwable throwable); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java deleted file mode 100644 index 934feaea6..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.eclipse.rap.rwt.client; - -import java.io.Serializable; - -import org.eclipse.rap.rwt.client.service.ClientService; - -public interface Client extends Serializable { - - /** - * Returns this client's implementation of a given service, if available. - * - * @param type the type of the requested service, must be a subtype of ClientService - * @return the requested service if provided by this client, otherwise null - * @see ClientService - */ - T getService( Class type ); - -} \ No newline at end of file diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java deleted file mode 100644 index 1f19bdd7c..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.eclipse.rap.rwt.client; - -public interface WebClient { - public final static String FAVICON = "rcp:FAVICON"; - public final static String PAGE_TITLE = "rcp:PAGE_TITLE"; - public final static String BODY_HTML = "rcp:BODY_HTML"; - public final static String THEME_ID = "rcp:THEME_ID"; - public final static String HEAD_HTML = "rcp:HEAD_HTML"; - public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW"; -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java deleted file mode 100644 index ffba4e43e..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.eclipse.rap.rwt.client.service; - -public interface BrowserNavigation extends ClientService { - void pushState(String state, String title); - - void addBrowserNavigationListener(BrowserNavigationListener listener); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java deleted file mode 100644 index 3e1b3eb72..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.eclipse.rap.rwt.client.service; - -public class BrowserNavigationEvent { - private String state; - - public String getState() { - return state; - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java deleted file mode 100644 index 8319c03f7..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.eclipse.rap.rwt.client.service; - -public interface BrowserNavigationListener { - public void navigated(BrowserNavigationEvent event); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java deleted file mode 100644 index 9f479d139..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.eclipse.rap.rwt.client.service; - -import java.io.Serializable; - -public interface ClientService extends Serializable { -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java deleted file mode 100644 index 6c44c729c..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.eclipse.rap.rwt.client.service; - -public interface JavaScriptExecutor extends ClientService { - public void execute( String code ); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java deleted file mode 100644 index 9dae811c7..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2012 EclipseSource and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * EclipseSource - initial API and implementation - ******************************************************************************/ -package org.eclipse.rap.rwt.client.service; - -/** - * The UrlLauncher service allows loading an URL in an external window, application or save dialog. - * - * @since 2.0 - * @noimplement This interface is not intended to be implemented by clients. - */ -public interface UrlLauncher extends ClientService { - - /** - * Opens the given URL. - * - * Any HTTP URL or relative URL will be opened in a new window. - * Modern browser may block any attempt to open new windows, but will usually prompt the user to - * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only - * to future attempts. It could also trigger a document reload, causing a session restart. - * - * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client - * to have a matching protocol handler registered. - * - * @param url the URL to open - */ - void openURL( String url ); - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java deleted file mode 100644 index 7e7116cf3..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.eclipse.rap.rwt.service; - -import java.io.IOException; -import java.io.InputStream; - -public interface ResourceLoader { - public InputStream getResourceAsStream(String resourceName) - throws IOException; -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java deleted file mode 100644 index c3379ea66..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.eclipse.rap.rwt.service; - -import java.io.InputStream; - -public interface ResourceManager { - public void register(String name, InputStream in); - - boolean unregister(String name); - - public InputStream getRegisteredContent(String name); - - public String getLocation(String name); - - public boolean isRegistered(String name); -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java deleted file mode 100644 index bed194f31..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.eclipse.rap.rwt.service; - -/** Mock, does nothing as this is irrelevant for RCP. */ -public class ServerPushSession { - public void start() { - - } - - public void stop() { - - } -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java deleted file mode 100644 index b2a2005e7..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.eclipse.rap.rwt.widgets; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Widget; - -public class DropDown { - private boolean visible=false; - - public DropDown(Widget parent, int style) { - // FIXME implement a shell - } - - public DropDown(Widget parent) { - this(parent, SWT.NONE); - } - - public void setVisible(boolean visible) { - this.visible = visible; - } - - public boolean isVisible() { - return visible; - } - - public void setItems( String[] items ) { - - } - - public void setSelectionIndex( int selection ) { - - } - -} diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java deleted file mode 100644 index cbf1449e0..000000000 --- a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.eclipse.rap.rwt.widgets; - -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Composite; - -public class FileUpload extends Composite { - - public FileUpload(Composite parent, int style) { - super(parent, style); - } - - public void addSelectionListener(SelectionListener listener) { - - } - - public void submit(String url) { - - } - - public void setImage(Image image) { - - } - - public void setText(String text) { - - } - - public String getFileName() { - return null; - } - - public String[] getFileNames() { - return null; - } - -} diff --git a/sdk/argeo-build b/sdk/argeo-build index 5029b67ae..a1d5c8e4b 160000 --- a/sdk/argeo-build +++ b/sdk/argeo-build @@ -1 +1 @@ -Subproject commit 5029b67ae930b82e822407b64d740173fe2d34c0 +Subproject commit a1d5c8e4bbee31d104fc1151cac4a8e19f5ef1fa diff --git a/sdk/branches/testing.bnd b/sdk/branches/testing.bnd new file mode 100644 index 000000000..b94c3b536 --- /dev/null +++ b/sdk/branches/testing.bnd @@ -0,0 +1,4 @@ +major=2 +minor=1 +micro=106 +qualifier=.next \ No newline at end of file diff --git a/sdk/branches/unstable.bnd b/sdk/branches/unstable.bnd new file mode 100644 index 000000000..98d75162f --- /dev/null +++ b/sdk/branches/unstable.bnd @@ -0,0 +1,4 @@ +major=2 +minor=3 +micro=14 +qualifier=.next \ No newline at end of file diff --git a/sdk/cms-e4-rap.properties b/sdk/cms-e4-rap.properties index a11ba7e1c..1ca557b7e 100644 --- a/sdk/cms-e4-rap.properties +++ b/sdk/cms-e4-rap.properties @@ -1,20 +1,18 @@ -argeo.osgi.start.2.node=\ +argeo.osgi.start.2=\ org.eclipse.equinox.http.servlet,\ -org.eclipse.equinox.metatype,\ -org.eclipse.equinox.cm,\ -org.eclipse.equinox.ds,\ +org.apache.felix.scr,\ org.eclipse.rap.rwt.osgi,\ org.argeo.init -argeo.osgi.start.3.node=\ -org.argeo.cms +argeo.osgi.start.3=\ +org.argeo.cms,\ +org.argeo.cms.swt.rap,\ +org.argeo.cms.swt.rcp,\ +org.argeo.cms.ee,\ +org.argeo.cms.lib.sshd,\ +org.argeo.cms.lib.equinox,\ +org.argeo.cms.lib.jetty,\ -argeo.osgi.start.4.node=\ -org.argeo.cms.servlet,\ -org.argeo.cms.jcr - -argeo.osgi.start.5.node=\ -org.argeo.cms.e4.rap # Local argeo.node.repo.type=h2 @@ -61,9 +59,11 @@ log.org.argeo=DEBUG # DON'T CHANGE BELOW org.eclipse.equinox.http.jetty.autostart=false -org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\ +org.osgi.framework.system.packages.extra=\ +com.sun.net.httpserver,\ +com.sun.jndi.ldap,\ com.sun.jndi.ldap.sasl,\ -com.sun.security.jgss,\ com.sun.jndi.dns,\ +com.sun.security.jgss,\ com.sun.nio.file,\ com.sun.nio.sctp diff --git a/sdk/cms-rcp.properties b/sdk/cms-rcp.properties new file mode 100644 index 000000000..df8363b76 --- /dev/null +++ b/sdk/cms-rcp.properties @@ -0,0 +1,39 @@ +argeo.osgi.start.2.node=\ +org.eclipse.equinox.metatype,\ +org.eclipse.equinox.cm,\ +org.eclipse.equinox.ds,\ +org.argeo.init + +argeo.osgi.start.3.node=\ +org.argeo.cms,\ +org.argeo.cms.jcr,\ +org.argeo.cms.ui.rcp + + +# Local +argeo.node.repo.type=h2 +org.osgi.service.http.port=7070 +#org.osgi.service.http.port.secure=7073 + +argeo.node.useradmin.uris=os:/// + +#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost:10389/dc=example,dc=com + +argeo.node.init=../../init + +argeo.i18n.locales=en,fr +argeo.i18n.defaultLocale=en + +#tika.config=/home/mbaudier/dev/git/gpl/argeo-suite/sdk/exec/argeo-office-e4-rap/data/indexes/node/tika-config.xml + +# Logging +log.org.argeo=DEBUG + +# DON'T CHANGE BELOW +org.eclipse.equinox.http.jetty.autostart=false +org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\ +com.sun.jndi.ldap.sasl,\ +com.sun.security.jgss,\ +com.sun.jndi.dns,\ +com.sun.nio.file,\ +com.sun.nio.sctp diff --git a/sdk/deploy/.gitignore b/sdk/deploy/.gitignore new file mode 100644 index 000000000..08eb0a07a --- /dev/null +++ b/sdk/deploy/.gitignore @@ -0,0 +1 @@ +!bin/ \ No newline at end of file diff --git a/sdk/deploy/argeo-cms-rcp/usr/bin/argeo-desktop-open b/sdk/deploy/argeo-cms-rcp/usr/bin/argeo-desktop-open new file mode 100755 index 000000000..17b1e88e1 --- /dev/null +++ b/sdk/deploy/argeo-cms-rcp/usr/bin/argeo-desktop-open @@ -0,0 +1,2 @@ +#!/bin/sh +curl $(cat $XDG_RUNTIME_DIR/argeo.rcp.url)$1 \ No newline at end of file diff --git a/sdk/deploy/argeo-cms/usr/bin/argeo b/sdk/deploy/argeo-cms/usr/bin/argeo new file mode 100755 index 000000000..636fd4769 --- /dev/null +++ b/sdk/deploy/argeo-cms/usr/bin/argeo @@ -0,0 +1,2 @@ +#!/bin/sh +java -Dorg.argeo.api.cli.rootCommand=$0 -jar /usr/share/a2/org.argeo.cms/org.argeo.cms.cli.2.3.jar "$@" \ No newline at end of file diff --git a/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.monitoring b/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.monitoring new file mode 100644 index 000000000..d7275ee48 --- /dev/null +++ b/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.monitoring @@ -0,0 +1 @@ +-Dcom.sun.management.jmxremote.port=8099 -Dcom.sun.management.jmxremote.rmi.port=8099 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname= \ No newline at end of file diff --git a/sdk/deploy/argeo-init/etc/argeo.user.d/jvm.args b/sdk/deploy/argeo-init/etc/argeo.user.d/jvm.args new file mode 100644 index 000000000..e69de29bb diff --git a/sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service b/sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service index 1cd43c9b9..2c69636ac 100644 --- a/sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service +++ b/sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service @@ -1,32 +1,42 @@ [Unit] Description=Argeo node %I -After=network.target +After=network-online.target Wants=postgresql.service [Service] Type=simple + +User=daemon +Group=daemon + StateDirectory=argeo.d/%I LogsDirectory=argeo.d/%I ConfigurationDirectory=argeo.d/%I CacheDirectory=argeo.d/%I WorkingDirectory=/var/lib/argeo.d/%I -ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java \ +ExecStart=java \ -Dosgi.configuration.cascaded=true \ --Dosgi.sharedConfiguration.area=/etc/argeo.d/%I \ +-Dosgi.sharedConfiguration.area=/etc/argeo.d/%I/ \ -Dosgi.sharedConfiguration.area.readOnly=true \ --Dosgi.configuration.area=/var/lib/argeo.d/%I/state \ --Dosgi.instance.area=/var/lib/argeo.d/%I/data \ --Dargeo.node.repo.indexesBase=/var/cache/argeo.d/%I/indexes \ --Dorg.osgi.framework.bootdelegation=com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp \ +-Dosgi.configuration.area=${STATE_DIRECTORY}/state/ \ +-Dosgi.instance.area=${STATE_DIRECTORY}/data/ \ +-Dargeo.node.repo.indexesBase=${CACHE_DIRECTORY}/indexes \ -Declipse.ignoreApp=true \ -Dosgi.noShutdown=true \ -Dorg.eclipse.equinox.http.jetty.autostart=false \ @/etc/argeo.d/jvm.args \ -@/etc/argeo.d/%I/jvm.args \ +@${CONFIGURATION_DIRECTORY}/jvm.args \ @/usr/share/argeo/jvm.args + # Exit codes of the JVM when SIGTERM or SIGINT have been caught: SuccessExitStatus=143 130 +CPUAccounting=true +MemoryAccounting=true +TasksAccounting=true +IOAccounting=true +IPAccounting=true + [Install] WantedBy=multi-user.target diff --git a/sdk/deploy/argeo-init/usr/lib/systemd/user/argeo@.service b/sdk/deploy/argeo-init/usr/lib/systemd/user/argeo@.service new file mode 100644 index 000000000..345685a97 --- /dev/null +++ b/sdk/deploy/argeo-init/usr/lib/systemd/user/argeo@.service @@ -0,0 +1,30 @@ +[Unit] +Description=Argeo user node %I + +[Service] +Type=simple +StateDirectory=argeo.d/%I +LogsDirectory=argeo.d/%I +ConfigurationDirectory=argeo.d/%I +CacheDirectory=argeo.d/%I +#WorkingDirectory= + +ExecStart=java \ +-Dosgi.configuration.cascaded=true \ +-Dosgi.sharedConfiguration.area=/etc/argeo.user.d/%I/ \ +-Dosgi.sharedConfiguration.area.readOnly=true \ +-Dosgi.configuration.area=${STATE_DIRECTORY}/state/ \ +-Dosgi.instance.area=${STATE_DIRECTORY}/data/ \ +-Dargeo.node.repo.indexesBase=${CACHE_DIRECTORY}/indexes \ +-Declipse.ignoreApp=true \ +-Dosgi.noShutdown=true \ +-Dorg.eclipse.equinox.http.jetty.autostart=false \ +-Djava.library.path=/usr/lib/a2/swt/rcp/org.argeo.tp.swt/ \ +@/etc/argeo.user.d/jvm.args \ +@/etc/argeo.user.d/%I/jvm.args \ +@/usr/share/argeo/jvm.args +# Exit codes of the JVM when SIGTERM or SIGINT have been caught: +SuccessExitStatus=143 130 + +[Install] +WantedBy=multi-user.target diff --git a/sdk/deploy/argeo-init/usr/share/argeo/jvm.args b/sdk/deploy/argeo-init/usr/share/argeo/jvm.args index 8d57ecb7e..2d3190d6f 100644 --- a/sdk/deploy/argeo-init/usr/share/argeo/jvm.args +++ b/sdk/deploy/argeo-init/usr/share/argeo/jvm.args @@ -1 +1 @@ --cp /usr/share/a2/org.argeo.tp.eclipse.equinox/org.eclipse.osgi.3.17.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.1.jar org.argeo.init.Service \ No newline at end of file +-cp /usr/share/a2/osgi/equinox/org.argeo.tp.osgi/org.eclipse.osgi.3.18.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.3.jar org.argeo.init.Service \ No newline at end of file diff --git a/sdk/init/node/.gitignore b/sdk/init/node/.gitignore deleted file mode 100644 index f61974476..000000000 --- a/sdk/init/node/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/krb5.keytab -/krb5.keytab.old -/*.p12 -/*.jks \ No newline at end of file diff --git a/sdk/init/node/ou=roles,ou=node.ldif b/sdk/init/node/ou=roles,ou=node.ldif deleted file mode 100644 index ffa9073ef..000000000 --- a/sdk/init/node/ou=roles,ou=node.ldif +++ /dev/null @@ -1,12 +0,0 @@ -dn: cn=admin,ou=roles,ou=node -objectClass: groupOfNames -objectClass: top -cn: admin -member: uid=root,ou=People,dc=example,dc=com - -dn: cn=userAdmin,ou=roles,ou=node -objectClass: groupOfNames -objectClass: top -member: cn=admin,ou=roles,ou=node -cn: userAdmin - diff --git a/sdk/init/private/.gitignore b/sdk/init/private/.gitignore new file mode 100644 index 000000000..f61974476 --- /dev/null +++ b/sdk/init/private/.gitignore @@ -0,0 +1,4 @@ +/krb5.keytab +/krb5.keytab.old +/*.p12 +/*.jks \ No newline at end of file diff --git a/sdk/init/private/ou=roles,ou=node.ldif b/sdk/init/private/ou=roles,ou=node.ldif new file mode 100644 index 000000000..ffa9073ef --- /dev/null +++ b/sdk/init/private/ou=roles,ou=node.ldif @@ -0,0 +1,12 @@ +dn: cn=admin,ou=roles,ou=node +objectClass: groupOfNames +objectClass: top +cn: admin +member: uid=root,ou=People,dc=example,dc=com + +dn: cn=userAdmin,ou=roles,ou=node +objectClass: groupOfNames +objectClass: top +member: cn=admin,ou=roles,ou=node +cn: userAdmin + diff --git a/swt/org.argeo.cms.e4/.classpath b/swt/org.argeo.cms.e4/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/org.argeo.cms.e4/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/org.argeo.cms.e4/.project b/swt/org.argeo.cms.e4/.project new file mode 100644 index 000000000..0c0406952 --- /dev/null +++ b/swt/org.argeo.cms.e4/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.e4 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml new file mode 100644 index 000000000..fcd3ae5cb --- /dev/null +++ b/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/org.argeo.cms.e4/bnd.bnd b/swt/org.argeo.cms.e4/bnd.bnd new file mode 100644 index 000000000..8839805c1 --- /dev/null +++ b/swt/org.argeo.cms.e4/bnd.bnd @@ -0,0 +1,20 @@ +Service-Component: OSGI-INF/defaultCallbackHandler.xml +Bundle-ActivationPolicy: lazy + +Import-Package: \ +org.argeo.api.acr,\ +org.eclipse.swt,\ +org.eclipse.swt.widgets;version="0.0.0",\ +org.eclipse.e4.ui.model.application.ui;resolution:=optional,\ +org.eclipse.e4.ui.model.application;resolution:=optional,\ +org.argeo.cms,\ +org.eclipse.core.commands.common,\ +org.eclipse.jface.window,\ +org.eclipse.jface.dialogs,\ +org.argeo.cms.swt.auth,\ +org.argeo.cms.ux.widgets,\ +javax.servlet.*;version="[3,5)",\ +org.eclipse.*;resolution:=optional,\ +javax.*;resolution:=optional,\ +* + diff --git a/swt/org.argeo.cms.e4/build.properties b/swt/org.argeo.cms.e4/build.properties new file mode 100644 index 000000000..e46a7baee --- /dev/null +++ b/swt/org.argeo.cms.e4/build.properties @@ -0,0 +1,9 @@ +output.. = bin/ +bin.includes = META-INF/,\ + OSGI-INF/,\ + .,\ + OSGI-INF/homeRepository.xml,\ + OSGI-INF/userAdminWrapper.xml,\ + OSGI-INF/defaultCallbackHandler.xml,\ + e4xmi/cms-demo.e4xmi +source.. = src/ diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java new file mode 100644 index 000000000..a997de748 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java @@ -0,0 +1,77 @@ +package org.argeo.cms.e4; + +import java.util.List; + +import org.argeo.cms.swt.CmsException; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.commands.MCommand; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem; +import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + +/** Static utilities simplifying recurring Eclipse 4 patterns. */ +public class CmsE4Utils { + /** Open an editor based on its id. */ + public static void openEditor(EPartService partService, String editorId, String key, String state) { + for (MPart part : partService.getParts()) { + String id = part.getPersistedState().get(key); + if (id != null && state.equals(id)) { + partService.showPart(part, PartState.ACTIVATE); + return; + } + } + + // new part + MPart part = partService.createPart(editorId); + if (part == null) + throw new CmsException("No editor found with id " + editorId); + part.getPersistedState().put(key, state); + partService.showPart(part, PartState.ACTIVATE); + } + + /** Dynamically creates an handled menu item from a command ID. */ + public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app, + String commandId) { + MCommand command = findCommand(modelService, app, commandId); + if (command == null) + return null; + MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class); + handledItem.setCommand(command); + return handledItem; + + } + + /** + * Finds a command by ID. + * + * @return the {@link MCommand} or null if not found. + */ + public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) { + List cmds = modelService.findElements(app, null, MCommand.class, null); + for (MCommand cmd : cmds) { + if (cmd.getElementId().equals(commandId)) { + return cmd; + } + } + return null; + } + + /** Dynamically creates a direct menu item from a class. */ + public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class clss, String label) { + MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); + dynamicItem.setLabel(label); + Bundle bundle = FrameworkUtil.getBundle(clss); + dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName()); + return dynamicItem; + } + + /** Singleton. */ + private CmsE4Utils() { + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java new file mode 100644 index 000000000..1e3e75cec --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java @@ -0,0 +1,33 @@ +package org.argeo.cms.e4; + +import org.argeo.cms.swt.CmsException; +import org.eclipse.e4.core.contexts.ContextFunction; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.core.di.IInjector; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */ +public class OsgiFilterContextFunction extends ContextFunction { + + private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext(); + + @Override + public Object compute(IEclipseContext context, String contextKey) { + ServiceReference[] srs; + try { + srs = bc.getServiceReferences((String) null, contextKey); + } catch (InvalidSyntaxException e) { + throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e); + } + if (srs == null || srs.length == 0) { + return IInjector.NOT_A_VALUE; + } else { + // return the first one + return bc.getService(srs[0]); + } + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java new file mode 100644 index 000000000..89055d2ff --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java @@ -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 privilegedAction = new PrivilegedAction() { + public IStatus run() { + return doRun(progressMonitor); + } + }; + return Subject.doAs(subject, privilegedAction); + } + + /** + * Implement here what should be executed with default context + * authentication + */ + protected abstract IStatus doRun(IProgressMonitor progressMonitor); +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java new file mode 100644 index 000000000..66a5ec8c7 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java @@ -0,0 +1,106 @@ +package org.argeo.cms.e4.addons; + +import java.security.AccessController; +import java.util.Iterator; + +import javax.annotation.PostConstruct; +import javax.security.auth.Subject; +import javax.servlet.http.HttpServletRequest; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.CmsException; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.MElementContainer; +import org.eclipse.e4.ui.model.application.ui.MUIElement; +import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; +import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; +import org.eclipse.e4.ui.model.application.ui.basic.MWindow; + +public class AuthAddon { + private final static CmsLog log = CmsLog.getLog(AuthAddon.class); + + public final static String AUTH = "auth."; + + @PostConstruct + void init(MApplication application) { + Iterator 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 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 container = (MElementContainer) element; + Iterator children = container.getChildren().iterator(); + while (children.hasNext()) { + MUIElement child = children.next(); + boolean visible = process(child); + if (!visible) + children.remove(); + } + + for (Object child : container.getChildren()) { + if (child instanceof MUIElement) { + boolean visible = process((MUIElement) child); + if (!visible) + container.getChildren().remove(child); + } + } + } + + return true; + } + + protected void logout() { + Subject subject = Subject.getSubject(AccessController.getContext()); + try { + CurrentUser.logoutCmsSession(subject); + } catch (Exception e) { + throw new CmsException("Cannot log out", e); + } + + // FIXME make it more generic + HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest(); + if (request != null) + request.getSession().setMaxInactiveInterval(0); + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java new file mode 100644 index 000000000..5bc0d6936 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java @@ -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 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 windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE, + new ElementMatcher(null, null, (String) null)); + for (MWindow window : windows) { + String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE); + int style = 0; + if (currentStyle != null) { + style = Integer.parseInt(currentStyle); + } + style = style | SWT.RIGHT_TO_LEFT; + window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style)); + } + } + } + } +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java new file mode 100644 index 000000000..6367b42d5 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse 4 addons to integrate with Argeo CMS. */ +package org.argeo.cms.e4.addons; \ No newline at end of file diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java new file mode 100644 index 000000000..416df7df1 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java @@ -0,0 +1,13 @@ +package org.argeo.cms.e4.handlers; + +import java.util.Locale; + +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.services.nls.ILocaleChangeService; + +public class ChangeLanguage { + @Execute + public void execute(ILocaleChangeService localeChangeService) { + localeChangeService.changeApplicationLocale(Locale.FRENCH); + } +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java new file mode 100644 index 000000000..9624c2d70 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java @@ -0,0 +1,136 @@ +package org.argeo.cms.e4.handlers; + +import static org.argeo.cms.CmsMsg.changePassword; +import static org.argeo.cms.CmsMsg.currentPassword; +import static org.argeo.cms.CmsMsg.newPassword; +import static org.argeo.cms.CmsMsg.passwordChanged; +import static org.argeo.cms.CmsMsg.repeatNewPassword; + +import java.util.Arrays; + +import javax.inject.Inject; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.keyring.CryptoKeyring; +import org.argeo.api.cms.transaction.WorkTransaction; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.dialogs.CmsFeedback; +import org.argeo.cms.swt.dialogs.CmsMessageDialog; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** Change the password of the logged-in user. */ +public class ChangePassword { + @Inject + private UserAdmin userAdmin; + @Inject + private WorkTransaction userTransaction; + @Inject + @Optional + private CryptoKeyring keyring = null; + + @Execute + public void execute() { + ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin); + if (dialog.open() == CmsDialog.OK) { + new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(), + CmsMessageDialog.INFORMATION).open(); + } + } + + protected void changePassword(char[] oldPassword, char[] newPassword) { + String name = CurrentUser.getUsername(); + LdapName dn; + try { + dn = new LdapName(name); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Invalid user dn " + name, e); + } + User user = (User) userAdmin.getRole(dn.toString()); + if (!user.hasCredential(null, oldPassword)) + throw new IllegalArgumentException("Invalid password"); + if (Arrays.equals(newPassword, new char[0])) + throw new IllegalArgumentException("New password empty"); + try { + userTransaction.begin(); + user.getCredentials().put(null, newPassword); + if (keyring != null) { + keyring.changePassword(oldPassword, newPassword); + // TODO change secret keys in the CMS session + } + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + e1.printStackTrace(); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new IllegalStateException("Cannot change password", e); + } + } + + class ChangePasswordDialog extends CmsMessageDialog { + private Text oldPassword, newPassword1, newPassword2; + + public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) { + super(parentShell, changePassword.lead(), CONFIRM); + } + +// protected Point getInitialSize() { +// return new Point(400, 450); +// } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + oldPassword = createLP(composite, currentPassword.lead()); + newPassword1 = createLP(composite, newPassword.lead()); + newPassword2 = createLP(composite, repeatNewPassword.lead()); + +// parent.pack(); + oldPassword.setFocus(); + return composite; + } + + @Override + protected void okPressed() { + try { + if (!newPassword1.getText().equals(newPassword2.getText())) + throw new IllegalArgumentException("New passwords are different"); + changePassword(oldPassword.getTextChars(), newPassword1.getTextChars()); + closeShell(CmsDialog.OK); + } catch (Exception e) { + CmsFeedback.error("Cannot change password", e); + } + } + + /** Creates label and password. */ + protected Text createLP(Composite parent, String label) { + new Label(parent, SWT.NONE).setText(label); + Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + return text; + } + + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java new file mode 100644 index 000000000..d11c0412c --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java @@ -0,0 +1,37 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class CloseAllParts { + + @Execute + void execute(EPartService partService) { + for (MPart part : partService.getParts()) { + if (part.isCloseable()) { + if (part.isDirty()) { + if (partService.savePart(part, true)) { + partService.hidePart(part, true); + } + } else { + partService.hidePart(part, true); + } + } + } + } + + @CanExecute + boolean canExecute(EPartService partService) { + boolean atLeastOnePart = false; + for (MPart part : partService.getParts()) { + if (part.isVisible() && part.isCloseable()) { + atLeastOnePart = true; + break; + } + } + return atLeastOnePart; + } + +} \ No newline at end of file diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java new file mode 100644 index 000000000..cce18020d --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java @@ -0,0 +1,26 @@ +package org.argeo.cms.e4.handlers; + +import javax.security.auth.Subject; + +import org.argeo.cms.CurrentUser; +import org.argeo.cms.util.CurrentSubject; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.workbench.IWorkbench; + +public class CloseWorkbench { + @Execute + public void execute(IWorkbench workbench) { + logout(); + workbench.close(); + } + + protected void logout() { + Subject subject = CurrentSubject.current(); + try { + CurrentUser.logoutCmsSession(subject); + } catch (Exception e) { + throw new IllegalStateException("Cannot log out", e); + } + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java new file mode 100644 index 000000000..358494c5b --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java @@ -0,0 +1,10 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.Execute; + +public class DoNothing { + @Execute + public void execute() { + + } +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java new file mode 100644 index 000000000..ac825bb0d --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java @@ -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 items, EModelService modelService) { + MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); + dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")"); + //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4"); + //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName()); + dynamicItem.setEnabled(true); + dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage"); + items.add(dynamicItem); + } + + @AboutToHide + public void aboutToHide() { + + } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java new file mode 100644 index 000000000..ac544b107 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java @@ -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 perspectives = modelService.findElements(application, perspectiveId, MPerspective.class, + null); + if (perspectives.size() == 0) + return; + MPerspective perspective = perspectives.get(0); + partService.switchPerspective(perspective); + } +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java new file mode 100644 index 000000000..3b60abd7e --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java @@ -0,0 +1,19 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class SaveAllParts { + + @Execute + void execute(EPartService partService) { + partService.saveAll(false); + } + + @CanExecute + boolean canExecute(EPartService partService) { + return partService.getDirtyParts().size() > 0; + } + +} \ No newline at end of file diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java new file mode 100644 index 000000000..73486f363 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java @@ -0,0 +1,18 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class SavePart { + @Execute + void execute(EPartService partService, MPart part) { + partService.savePart(part, false); + } + + @CanExecute + boolean canExecute(MPart part) { + return part.isDirty(); + } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java new file mode 100644 index 000000000..a44ca9056 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java @@ -0,0 +1,2 @@ +/** Generic Eclipse 4 handlers. */ +package org.argeo.cms.e4.handlers; \ No newline at end of file diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java new file mode 100644 index 000000000..233119c0d --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse 4 user interfaces. */ +package org.argeo.cms.e4; \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/.classpath b/swt/org.argeo.cms.swt/.classpath new file mode 100644 index 000000000..248abe0e1 --- /dev/null +++ b/swt/org.argeo.cms.swt/.classpath @@ -0,0 +1,9 @@ + + + + + + + diff --git a/swt/org.argeo.cms.swt/.project b/swt/org.argeo.cms.swt/.project new file mode 100644 index 000000000..8ac021b59 --- /dev/null +++ b/swt/org.argeo.cms.swt/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.swt + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/org.argeo.cms.swt/META-INF/.gitignore b/swt/org.argeo.cms.swt/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/org.argeo.cms.swt/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/org.argeo.cms.swt/OSGI-INF/cmsUserApp.xml b/swt/org.argeo.cms.swt/OSGI-INF/cmsUserApp.xml new file mode 100644 index 000000000..4f2a405d5 --- /dev/null +++ b/swt/org.argeo.cms.swt/OSGI-INF/cmsUserApp.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/swt/org.argeo.cms.swt/bnd.bnd b/swt/org.argeo.cms.swt/bnd.bnd new file mode 100644 index 000000000..1dfa6599d --- /dev/null +++ b/swt/org.argeo.cms.swt/bnd.bnd @@ -0,0 +1,12 @@ +Import-Package: org.eclipse.swt,\ +org.eclipse.jface.window,\ +org.eclipse.jface.dialogs,\ +org.eclipse.core.commands.common,\ +javax.servlet.*;version="[3,5)",\ +* + +Bundle-ActivationPolicy: lazy + +Service-Component: \ +OSGI-INF/cmsUserApp.xml + \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/build.properties b/swt/org.argeo.cms.swt/build.properties new file mode 100644 index 000000000..5f0f21af9 --- /dev/null +++ b/swt/org.argeo.cms.swt/build.properties @@ -0,0 +1,5 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/cmsUserApp.xml +source.. = src/ diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/jface/dialog/CmsWizardDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/jface/dialog/CmsWizardDialog.java new file mode 100644 index 000000000..ad347e6e8 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/jface/dialog/CmsWizardDialog.java @@ -0,0 +1,223 @@ +package org.argeo.cms.jface.dialog; + +import java.lang.reflect.InvocationTargetException; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.Selected; +import org.argeo.cms.swt.dialogs.LightweightDialog; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.IWizardContainer2; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** A wizard dialog based on {@link LightweightDialog}. */ +public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 { + private static final long serialVersionUID = -2123153353654812154L; + + private IWizard wizard; + private IWizardPage currentPage; + private int currentPageIndex; + + private Label titleBar; + private Label message; + private Composite[] pageBodies; + private Composite buttons; + private Button back; + private Button next; + private Button finish; + + public CmsWizardDialog(Shell parentShell, IWizard wizard) { + super(parentShell); + this.wizard = wizard; + wizard.setContainer(this); + // create the pages + wizard.addPages(); + currentPage = wizard.getStartingPage(); + if (currentPage == null) + throw new IllegalArgumentException("At least one wizard page is required"); + } + + @Override + protected Control createDialogArea(Composite parent) { + updateWindowTitle(); + + Composite messageArea = new Composite(parent, SWT.NONE); + messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + { + messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); + titleBar = new Label(messageArea, SWT.WRAP); + titleBar.setFont(EclipseUiUtils.getBoldFont(parent)); + titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false)); + updateTitleBar(); + Button cancelButton = new Button(messageArea, SWT.FLAT); + cancelButton.setText(CmsMsg.cancel.lead()); + cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3)); + cancelButton.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.CANCEL)); + message = new Label(messageArea, SWT.WRAP); + message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2)); + updateMessage(); + } + + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new FormLayout()); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + pageBodies = new Composite[wizard.getPageCount()]; + IWizardPage[] pages = wizard.getPages(); + for (int i = 0; i < pages.length; i++) { + pageBodies[i] = new Composite(body, SWT.NONE); + pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout()); + setSwitchingFormData(pageBodies[i]); + pages[i].createControl(pageBodies[i]); + } + showPage(currentPage); + + buttons = new Composite(parent, SWT.NONE); + buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + { + boolean singlePage = wizard.getPageCount() == 1; + // singlePage = false;// dev + GridLayout layout = new GridLayout(singlePage ? 1 : 3, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + // TODO revert order for right-to-left languages + + if (!singlePage) { + back = new Button(buttons, SWT.PUSH); + back.setText(CmsMsg.wizardBack.lead()); + back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + back.addSelectionListener((Selected) (e) -> backPressed()); + + next = new Button(buttons, SWT.PUSH); + next.setText(CmsMsg.wizardNext.lead()); + next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + next.addSelectionListener((Selected) (e) -> nextPressed()); + } + finish = new Button(buttons, SWT.PUSH); + finish.setText(CmsMsg.wizardFinish.lead()); + finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + finish.addSelectionListener((Selected) (e) -> finishPressed()); + + updateButtons(); + } + return body; + } + + @Override + public IWizardPage getCurrentPage() { + return currentPage; + } + + @Override + public Shell getShell() { + return getForegoundShell(); + } + + @Override + public void showPage(IWizardPage page) { + IWizardPage[] pages = wizard.getPages(); + int index = -1; + for (int i = 0; i < pages.length; i++) { + if (page == pages[i]) { + index = i; + break; + } + } + if (index < 0) + throw new IllegalArgumentException("Cannot find index of wizard page " + page); + pageBodies[index].moveAbove(pageBodies[currentPageIndex]); + + // // clear + // for (Control c : body.getChildren()) + // c.dispose(); + // page.createControl(body); + // body.layout(true, true); + currentPageIndex = index; + currentPage = page; + } + + @Override + public void updateButtons() { + if (back != null) + back.setEnabled(wizard.getPreviousPage(currentPage) != null); + if (next != null) + next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage()); + if (finish != null) { + finish.setEnabled(wizard.canFinish()); + } + } + + @Override + public void updateMessage() { + if (currentPage.getMessage() != null) + message.setText(currentPage.getMessage()); + } + + @Override + public void updateTitleBar() { + if (currentPage.getTitle() != null) + titleBar.setText(currentPage.getTitle()); + } + + @Override + public void updateWindowTitle() { + setTitle(wizard.getWindowTitle()); + } + + @Override + public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) + throws InvocationTargetException, InterruptedException { + // FIXME it creates a dependency to Eclipse Core Runtime + // runnable.run(null); + } + + @Override + public void updateSize() { + // TODO pack? + } + + protected boolean onCancel() { + return wizard.performCancel(); + } + + protected void nextPressed() { + IWizardPage page = wizard.getNextPage(currentPage); + showPage(page); + updateButtons(); + } + + protected void backPressed() { + IWizardPage page = wizard.getPreviousPage(currentPage); + showPage(page); + updateButtons(); + } + + protected void finishPressed() { + if (wizard.performFinish()) + closeShell(CmsDialog.OK); + } + + private static void setSwitchingFormData(Composite composite) { + FormData fdLabel = new FormData(); + fdLabel.top = new FormAttachment(0, 0); + fdLabel.left = new FormAttachment(0, 0); + fdLabel.right = new FormAttachment(100, 0); + fdLabel.bottom = new FormAttachment(100, 0); + composite.setLayoutData(fdLabel); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java new file mode 100644 index 000000000..06bb9be37 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java @@ -0,0 +1,141 @@ +package org.argeo.cms.swt; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; + +import org.argeo.api.cms.CmsApp; +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsUi; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.util.CurrentSubject; +import org.eclipse.swt.widgets.Display; + +public abstract class AbstractSwtCmsView implements CmsView { + private final static CmsLog log = CmsLog.getLog(AbstractSwtCmsView.class); + + protected final String uiName; + + protected LoginContext loginContext; + protected String state; +// protected Throwable exception; + protected UxContext uxContext; + protected CmsImageManager imageManager; + + protected Display display; + protected CmsUi ui; + + protected String uid; + + public AbstractSwtCmsView(String uiName) { + this.uiName = uiName; + } + + public abstract CmsEventBus getCmsEventBus(); + + public abstract CmsApp getCmsApp(); + + @Override + public void sendEvent(String topic, Map properties) { + if (properties == null) + properties = new HashMap<>(); + if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid)) + throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid (" + + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid); + properties.put(CMS_VIEW_UID_PROPERTY, uid); + + log.trace(() -> uid + ": send event to " + topic); + + getCmsEventBus().sendEvent(topic, properties); + // getCmsApp().onEvent(topic, properties); + } + +// public void runAs(Runnable runnable) { +// display.asyncExec(() -> doAs(Executors.callable(runnable))); +// } + + public T doAs(Callable action) { + try { + CompletableFuture result = new CompletableFuture<>(); + Runnable toDo = () -> { + log.trace(() -> uid + ": process doAs"); + Subject subject = CurrentSubject.current(); + T res; + if (subject != null) { + assert subject == getSubject(); + try { + res = action.call(); + } catch (Exception e) { + throw new CompletionException("Failed to execute action for " + subject, e); + } + } else { + res = CurrentSubject.callAs(getSubject(), action); + } + result.complete(res); + }; + if (Thread.currentThread() == display.getThread()) + toDo.run(); + else { + display.asyncExec(toDo); + display.wake(); + } +// throw new IllegalStateException("Must be called from UI thread"); + return result.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot execute action ins CMS view " + uid, e); + } + } + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public String getUid() { + return uid; + } + + @Override + public CmsImageManager getImageManager() { + return imageManager; + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public Object getData(String key) { + if (ui != null) { + return ui.getData(key); + } else { + throw new IllegalStateException("UI is not initialized"); + } + } + + @Override + public void setData(String key, Object value) { + if (ui != null) { + ui.setData(key, value); + } else { + throw new IllegalStateException("UI is not initialized"); + } + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java new file mode 100644 index 000000000..00a51ef92 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java @@ -0,0 +1,77 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.cms.ux.AbstractImageManager; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +/** Manages only public images so far. */ +public abstract class AbstractSwtImageManager extends AbstractImageManager { + protected abstract Image getSwtImage(M node); + + protected abstract String noImg(Cms2DSize size); + + public Boolean load(M node, Control control, Cms2DSize preferredSize) { + Cms2DSize imageSize = getImageSize(node); + Cms2DSize size; + String imgTag = null; + if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0 + || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) { + if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) { + // actual image size if completely known + size = imageSize; + } else { + // no image if not completely known + size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize); + imgTag = noImg(size); + } + + } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) { + // given size if completely provided + size = preferredSize; + } else { + // at this stage : + // image is completely known + assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0; + // one and only one of the dimension as been specified + assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0; + size = resizeTo(imageSize, preferredSize); + } + + boolean loaded = false; + if (control == null) + return loaded; + + if (control instanceof Label) { + if (imgTag == null) { + // IMAGE RETRIEVED HERE + imgTag = getImageTag(node, size); + // + if (imgTag == null) + imgTag = noImg(size); + else + loaded = true; + } + + Label lbl = (Label) control; + lbl.setText(imgTag); + // lbl.setSize(size); +// } else if (control instanceof FileUpload) { +// FileUpload lbl = (FileUpload) control; +// lbl.setImage(CmsUiUtils.noImage(size)); +// lbl.setSize(new Point(size.getWidth(), size.getHeight())); +// return loaded; + } else + loaded = false; + + return loaded; + } + + public Cms2DSize getImageSize(M node) { + // TODO optimise + Image image = getSwtImage(node); + return new Cms2DSize(image.getBounds().width, image.getBounds().height); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsException.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsException.java new file mode 100644 index 000000000..874ea9691 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsException.java @@ -0,0 +1,16 @@ +package org.argeo.cms.swt; + +/** @deprecated Use standard Java {@link RuntimeException} instead. */ +@Deprecated +public class CmsException extends RuntimeException { + private static final long serialVersionUID = -5341764743356771313L; + + public CmsException(String message) { + super(message); + } + + public CmsException(String message, Throwable e) { + super(message, e); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java new file mode 100644 index 000000000..9eba6f6ec --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java @@ -0,0 +1,32 @@ +package org.argeo.cms.swt; + +/** Styles references in the CSS. */ +@Deprecated +public interface CmsStyles { + // General + public final static String CMS_SHELL = "cms_shell"; + public final static String CMS_MENU_LINK = "cms_menu_link"; + + // Header + public final static String CMS_HEADER = "cms_header"; + public final static String CMS_HEADER_LEAD = "cms_header-lead"; + public final static String CMS_HEADER_CENTER = "cms_header-center"; + public final static String CMS_HEADER_END = "cms_header-end"; + + public final static String CMS_LEAD = "cms_lead"; + public final static String CMS_END = "cms_end"; + public final static String CMS_FOOTER = "cms_footer"; + + public final static String CMS_USER_MENU = "cms_user_menu"; + public final static String CMS_USER_MENU_LINK = "cms_user_menu-link"; + public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item"; + public final static String CMS_LOGIN_DIALOG = "cms_login_dialog"; + public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username"; + public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password"; + + // Body + public final static String CMS_SCROLLED_AREA = "cms_scrolled_area"; + public final static String CMS_BODY = "cms_body"; + public final static String CMS_STATIC_TEXT = "cms_static-text"; + public final static String CMS_LINK = "cms_link"; +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java new file mode 100644 index 000000000..7669b1554 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java @@ -0,0 +1,25 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.ux.CmsIcon; +import org.argeo.api.cms.ux.CmsTheme; +import org.eclipse.swt.graphics.Image; + +/** SWT specific {@link CmsTheme}. */ +public interface CmsSwtTheme extends CmsTheme { +// /** The image registered at this path, or null 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 null if not found. + * + * @param name An icon file name without path and extension. + * @param preferredSize the preferred size, if null, + * {@link #getDefaultIconSize()} will be tried. + */ + Image getIcon(String name, Integer preferredSize); + + Image getSmallIcon(CmsIcon icon); + + Image getBigIcon(CmsIcon icon); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java new file mode 100644 index 000000000..e0f63e45e --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java @@ -0,0 +1,26 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.ux.CmsUi; +import org.argeo.api.cms.ux.CmsView; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; + +/** A basic {@link CmsUi}, based on an SWT {@link Composite}. */ +public class CmsSwtUi extends Composite implements CmsUi { + + private static final long serialVersionUID = -107939076610406448L; + + private CmsView cmsView; + + public CmsSwtUi(Composite parent, int style) { + super(parent, style); + cmsView = CmsSwtUtils.getCmsView(parent); + + setLayout(new GridLayout()); + } + + public CmsView getCmsView() { + return cmsView; + } + +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java new file mode 100644 index 000000000..5d964090b --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java @@ -0,0 +1,315 @@ +package org.argeo.cms.swt; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.argeo.api.cms.ux.CmsIcon; +import org.argeo.api.cms.ux.CmsStyle; +import org.argeo.api.cms.ux.CmsTheme; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Widget; + +/** SWT utilities. */ +public class CmsSwtUtils { + /* + * THEME AND VIEW + */ + + public static CmsSwtTheme getCmsTheme(Composite parent) { + CmsSwtTheme theme = (CmsSwtTheme) parent.getData(CmsTheme.class.getName()); + if (theme == null) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + theme = (CmsSwtTheme) topShell.getData(CmsTheme.class.getName()); + parent.setData(CmsTheme.class.getName(), theme); + } + return theme; + } + + public static void registerCmsTheme(Shell shell, CmsTheme theme) { + // find parent shell + Shell topShell = shell; + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + // check if already set + if (topShell.getData(CmsTheme.class.getName()) != null) { + CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); + throw new IllegalArgumentException( + "Theme " + registeredTheme.getThemeId() + " already registered in this shell"); + } + topShell.setData(CmsTheme.class.getName(), theme); + } + + public static CmsView getCmsView(Control parent) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + return (CmsView) topShell.getData(CmsView.class.getName()); + } + + public static void registerCmsView(Shell shell, CmsView view) { + // find parent shell + Shell topShell = shell; + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + // check if already set + if (topShell.getData(CmsView.class.getName()) != null) { + CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName()); + throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell"); + } + shell.setData(CmsView.class.getName(), view); + } + + /* + * EVENTS + */ + + /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */ + public static void sendEventOnSelect(Control control, String topic, Map 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 properties = new HashMap<>(); + properties.put(key, value); + sendEventOnSelect(control, topic, properties); + } + + /* + * ICONS + */ + /** Get a small icon from this theme. */ + public static Image getSmallIcon(CmsTheme theme, CmsIcon icon) { + return ((CmsSwtTheme) theme).getSmallIcon(icon); + } + + /** Get a big icon from this theme. */ + public static Image getBigIcon(CmsTheme theme, CmsIcon icon) { + return ((CmsSwtTheme) theme).getBigIcon(icon); + } + + /* + * LAYOUT INDEPENDENT + */ + /** Takes the most space possible, depending on parent layout. */ + public static void fill(Control control) { + Layout parentLayout = control.getParent().getLayout(); + if (parentLayout == null) + throw new IllegalStateException("Parent layout is not set"); + if (parentLayout instanceof GridLayout) { + control.setLayoutData(fillAll()); + } else if (parentLayout instanceof FormLayout) { + control.setLayoutData(coverAll()); + } else { + throw new IllegalArgumentException("Unsupported parent layout " + parentLayout.getClass().getName()); + } + } + + /* + * GRID LAYOUT + */ + /** A {@link GridLayout} without any spacing and one column. */ + public static GridLayout noSpaceGridLayout() { + return noSpaceGridLayout(new GridLayout()); + } + + /** + * A {@link GridLayout} without any spacing and multiple columns of unequal + * width. + */ + public static GridLayout noSpaceGridLayout(int columns) { + return noSpaceGridLayout(new GridLayout(columns, false)); + } + + /** @return the same layout, with spaces removed. */ + public static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static GridData fillAll() { + return new GridData(SWT.FILL, SWT.FILL, true, true); + } + + public static GridData fillWidth() { + return grabWidth(SWT.FILL, SWT.FILL); + } + + public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, true, false); + } + + public static GridData fillHeight() { + return grabHeight(SWT.FILL, SWT.FILL); + } + + public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, false, true); + } + + /* + * ROW LAYOUT + */ + /** @return the same layout, with margins removed. */ + public static RowLayout noMarginsRowLayout(RowLayout rowLayout) { + rowLayout.marginTop = 0; + rowLayout.marginBottom = 0; + rowLayout.marginLeft = 0; + rowLayout.marginRight = 0; + return rowLayout; + } + + public static RowLayout noMarginsRowLayout(int type) { + return noMarginsRowLayout(new RowLayout(type)); + } + + public static RowData rowData16px() { + return new RowData(16, 16); + } + + /* + * FORM LAYOUT + */ + public static FormData coverAll() { + FormData fdLabel = new FormData(); + fdLabel.top = new FormAttachment(0, 0); + fdLabel.left = new FormAttachment(0, 0); + fdLabel.right = new FormAttachment(100, 0); + fdLabel.bottom = new FormAttachment(100, 0); + return fdLabel; + } + + /* + * STYLING + */ + + /** Style widget */ + public static T 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 style(T widget, CmsStyle style) { + return style(widget, style.style()); + } + + /** Enable markups on widget */ + public static T markup(T widget) { + EclipseUiSpecificUtils.setMarkupData(widget); + return widget; + } + + /** Disable markup validation. */ + public static 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 toString() method. + * This argument should not be null, but if it is null and + * assertions are disabled "" is displayed instead; if + * assertions are enabled the call will fail. + * + * @see markup + */ + public static T text(T widget, Object txt) { + assert txt != null; + String str = txt != null ? txt.toString() : ""; + markup(widget); + if (widget instanceof Label) + ((Label) widget).setText(str); + else if (widget instanceof Button) + ((Button) widget).setText(str); + else if (widget instanceof Text) + ((Text) widget).setText(str); + else + throw new IllegalArgumentException("Unsupported widget type " + widget.getClass()); + return widget; + } + + /** A {@link Label} with markup activated. */ + public static Label lbl(Composite parent, Object txt) { + return text(new Label(parent, SWT.NONE), txt); + } + + /** A read-only {@link Text} whose content can be copy/pasted. */ + public static Text txt(Composite parent, Object txt) { + return text(new Text(parent, SWT.NONE), txt); + } + + /** Dispose all children of a Composite */ + public static void clear(Composite composite) { + if (composite.isDisposed()) + return; + for (Control child : composite.getChildren()) + child.dispose(); + } + + /** Clean reserved URL characters for use in HTTP links. */ + public static String cleanPathForUrl(String path) { + StringTokenizer st = new StringTokenizer(path, "/"); + StringBuilder sb = new StringBuilder(); + while (st.hasMoreElements()) { + sb.append('/'); + String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8); + encoded = encoded.replace("+", "%20"); + sb.append(encoded); + + } + return sb.toString(); + } + + /** Singleton. */ + private CmsSwtUtils() { + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java new file mode 100644 index 000000000..c664aa3ca --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java @@ -0,0 +1,26 @@ +package org.argeo.cms.swt; + +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; + +/** + * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface + * in order to use as a short lambda expression in UI code. + * {@link MouseListener#mouseDown(MouseEvent)} and + * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. + */ +@FunctionalInterface +public interface MouseDoubleClick extends MouseListener { + @Override + void mouseDoubleClick(MouseEvent e); + + @Override + default void mouseDown(MouseEvent e) { + // does nothing + } + + @Override + default void mouseUp(MouseEvent e) { + // does nothing + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java new file mode 100644 index 000000000..baecb0072 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java @@ -0,0 +1,26 @@ +package org.argeo.cms.swt; + +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; + +/** + * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in + * order to use as a short lambda expression in UI code. + * {@link MouseListener#mouseDoubleClick(MouseEvent)} and + * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. + */ +@FunctionalInterface +public interface MouseDown extends MouseListener { + @Override + void mouseDown(MouseEvent e); + + @Override + default void mouseDoubleClick(MouseEvent e) { + // does nothing + } + + @Override + default void mouseUp(MouseEvent e) { + // does nothing + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java new file mode 100644 index 000000000..03fbad01e --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java @@ -0,0 +1,21 @@ +package org.argeo.cms.swt; + +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; + +/** + * {@link SelectionListener} as a functional interface in order to use as a + * short lambda expression in UI code. + * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing + * by default. + */ +@FunctionalInterface +public interface Selected extends SelectionListener { + @Override + public void widgetSelected(SelectionEvent e); + + default public void widgetDefaultSelected(SelectionEvent e) { + // does nothing + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java new file mode 100644 index 000000000..e468c6d52 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java @@ -0,0 +1,50 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.ux.UxContext; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; + +public class SimpleSwtUxContext implements UxContext { + private Point size; + private Point small = new Point(400, 400); + + public SimpleSwtUxContext() { + this(Display.getCurrent().getBounds()); + } + + public SimpleSwtUxContext(Rectangle rect) { + this.size = new Point(rect.width, rect.height); + } + + public SimpleSwtUxContext(Point size) { + this.size = size; + } + + @Override + public boolean isPortrait() { + return size.x >= size.y; + } + + @Override + public boolean isLandscape() { + return size.x < size.y; + } + + @Override + public boolean isSquare() { + return size.x == size.y; + } + + @Override + public boolean isSmall() { + return size.x <= small.x || size.y <= small.y; + } + + @Override + public boolean isMasterData() { + // TODO make it configurable + return true; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SwtEditablePart.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SwtEditablePart.java new file mode 100644 index 000000000..f2cceef07 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SwtEditablePart.java @@ -0,0 +1,9 @@ +package org.argeo.cms.swt; + +import org.argeo.cms.ux.widgets.EditablePart; +import org.eclipse.swt.widgets.Control; + +/** Manages whether an editable or non editable control is shown. */ +public interface SwtEditablePart extends EditablePart { + public Control getControl(); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java new file mode 100644 index 000000000..cf05f6f64 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java @@ -0,0 +1,337 @@ +package org.argeo.cms.swt.acr; + +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsEditable; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.SwtEditablePart; +import org.argeo.cms.swt.widgets.ScrolledPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.SAXParseException; + +/** Base class for viewers related to a page */ +public abstract class AbstractPageViewer { + + private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class); + + private final boolean readOnly; + /** The basis for the layouts, typically a ScrolledPage. */ + private final Composite page; + private final CmsEditable cmsEditable; + + private MouseListener mouseListener; + private FocusListener focusListener; + + private SwtEditablePart edited; +// private ISelection selection = StructuredSelection.EMPTY; + + private Subject viewerSubject; + + protected AbstractPageViewer(Composite parent, int style, CmsEditable cmsEditable) { + // read only at UI level + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + + this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable; +// if (this.cmsEditable instanceof Observable) +// ((Observable) this.cmsEditable).addObserver(this); + + if (cmsEditable.canEdit()) { + mouseListener = createMouseListener(); + focusListener = createFocusListener(); + } + page = findPage(parent); +// accessControlContext = AccessController.getContext(); + viewerSubject = CurrentUser.getCmsSession().getSubject(); + } + + public abstract Control getControl(); + +// /** +// * Can be called to simplify the called to isModelInitialized() and initModel() +// */ +// protected void initModelIfNeeded(Node node) { +// try { +// if (!isModelInitialized(node)) +// if (getCmsEditable().canEdit()) { +// initModel(node); +// node.getSession().save(); +// } +// } catch (RepositoryException e) { +// throw new JcrException("Cannot initialize model", e); +// } +// } +// +// /** Called if user can edit and model is not initialized */ +// protected Boolean isModelInitialized(Node node) throws RepositoryException { +// return true; +// } +// +// /** Called if user can edit and model is not initialized */ +// protected void initModel(Node node) throws RepositoryException { +// } + + /** Create (retrieve) the MouseListener to use. */ + protected MouseListener createMouseListener() { + return new MouseAdapter() { + private static final long serialVersionUID = 1L; + }; + } + + /** Create (retrieve) the FocusListener to use. */ + protected FocusListener createFocusListener() { + return new FocusListener() { + private static final long serialVersionUID = 1L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + } + }; + } + + protected Composite findPage(Composite composite) { + if (composite instanceof ScrolledPage) { + return (ScrolledPage) composite; + } else { + if (composite.getParent() == null) + return composite; + return findPage(composite.getParent()); + } + } + + public void layoutPage() { + if (page != null) + page.layout(true, true); + } + + protected void showControl(Control control) { + if (page != null && (page instanceof ScrolledPage)) + ((ScrolledPage) page).showControl(control); + } + +// @Override +// public void update(Observable o, Object arg) { +// if (o == cmsEditable) +// editingStateChanged(cmsEditable); +// } + + /** To be overridden in order to provide the actual refresh */ + protected void refresh(Control control) { + } + + /** To be overridden.Save the edited part. */ + protected void save(SwtEditablePart part) { + } + + /** Prepare the edited part */ + protected void prepare(SwtEditablePart part, Object caretPosition) { + } + + /** Notified when the editing state changed. Does nothing, to be overridden */ + protected void editingStateChanged(CmsEditable cmsEditable) { + } + + public void refresh() { + // TODO check actual context in order to notice a discrepancy + Subject viewerSubject = getViewerSubject(); + Subject.doAs(viewerSubject, (PrivilegedAction) () -> { + if (cmsEditable.canEdit() && !readOnly) + mouseListener = createMouseListener(); + else + mouseListener = null; + refresh(getControl()); + // layout(getControl()); + if (!getControl().isDisposed()) + layoutPage(); + return null; + }); + } + +// @Override +// public void setSelection(ISelection selection, boolean reveal) { +// this.selection = selection; +// } + + protected void updateContent(SwtEditablePart part) { + } + + // LOW LEVEL EDITION + protected void edit(SwtEditablePart part, Object caretPosition) { + if (edited == part) + return; + + if (edited != null && edited != part) { + SwtEditablePart previouslyEdited = edited; + try { + stopEditing(true); + } catch (Exception e) { + notifyEditionException(e); + edit(previouslyEdited, caretPosition); + return; + } + } + + part.startEditing(); + edited = part; + updateContent(part); + prepare(part, caretPosition); + edited.getControl().addFocusListener(new FocusListener() { + private static final long serialVersionUID = 6883521812717097017L; + + @Override + public void focusLost(FocusEvent event) { + stopEditing(true); + } + + @Override + public void focusGained(FocusEvent event) { + } + }); + + layout(part.getControl()); + showControl(part.getControl()); + } + + protected void stopEditing(Boolean save) { + if (edited instanceof Widget && ((Widget) edited).isDisposed()) { + edited = null; + return; + } + + assert edited != null; + if (edited == null) { + if (log.isTraceEnabled()) + log.warn("Told to stop editing while not editing anything"); + return; + } + + try { + if (save) + save(edited); + + edited.stopEditing(); + SwtEditablePart editablePart = edited; + Control control = ((SwtEditablePart) edited).getControl(); + edited = null; + // TODO make edited state management more robust + updateContent(editablePart); + layout(control); + } finally { + edited = null; + } + } + + // METHODS AVAILABLE TO EXTENDING CLASSES + protected void saveEdit() { + if (edited != null) + stopEditing(true); + } + + protected void cancelEdit() { + if (edited != null) + stopEditing(false); + } + + /** Layout this controls from the related base page. */ + public void layout(Control... controls) { + page.layout(controls); + } + + /** + * Find the first {@link SwtEditablePart} in the parents hierarchy of this + * control + */ + protected SwtEditablePart findDataParent(Control parent) { + if (parent instanceof SwtEditablePart) { + return (SwtEditablePart) parent; + } + if (parent.getParent() != null) + return findDataParent(parent.getParent()); + else + throw new IllegalStateException("No data parent found"); + } + + // UTILITIES + /** Check whether the edited part is in a proper state */ + protected void checkEdited() { + if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed()) + throw new IllegalStateException("Edited should not be null or disposed at this stage"); + } + + /** Persist all changes. */ + protected void persistChanges(ContentSession session) { +// session.save(); +// session.refresh(false); + // TODO notify that changes have been persisted + } + + /** Convenience method using a Node in order to save the underlying session. */ + protected void persistChanges(Content anyNode) { + persistChanges(((ProvidedContent) anyNode).getSession()); + } + + /** Notify edition exception */ + protected void notifyEditionException(Throwable e) { + Throwable eToLog = e; + if (e instanceof IllegalArgumentException) + if (e.getCause() instanceof SAXParseException) + eToLog = e.getCause(); + log.error(eToLog.getMessage(), eToLog); +// if (log.isTraceEnabled()) +// log.trace("Full stack of " + eToLog.getMessage(), e); + // TODO Light error notification popup + } + + protected Subject getViewerSubject() { + return viewerSubject; +// Subject res = null; +// if (accessControlContext != null) { +// res = Subject.getSubject(accessControlContext); +// } +// if (res == null) +// throw new IllegalStateException("No subject associated with this viewer"); +// return res; + } + + // GETTERS / SETTERS + public boolean isReadOnly() { + return readOnly; + } + + protected SwtEditablePart getEdited() { + return edited; + } + + public MouseListener getMouseListener() { + return mouseListener; + } + + public FocusListener getFocusListener() { + return focusListener; + } + + public CmsEditable getCmsEditable() { + return cmsEditable; + } + +// @Override +// public ISelection getSelection() { +// return selection; +// } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java new file mode 100644 index 000000000..78a993005 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java @@ -0,0 +1,48 @@ +package org.argeo.cms.swt.acr; + +import java.io.InputStream; + +import org.argeo.api.acr.Content; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.cms.swt.AbstractSwtImageManager; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.CmsUxUtils; +import org.eclipse.swt.graphics.Image; + +public class AcrSwtImageManager extends AbstractSwtImageManager { + + @Override + public String getImageUrl(Content node) { + return getDataPathForUrl(node); + } + + @Override + public String uploadImage(Content context, Content uploadFolder, String fileName, InputStream in, + String contentType) { + throw new UnsupportedOperationException(); + } + + @Override + protected Image getSwtImage(Content node) { + throw new UnsupportedOperationException(); + } + + @Override + protected String noImg(Cms2DSize size) { + String dataPath = ""; + return CmsUxUtils.img(dataPath, size); + } + + protected String getDataPathForUrl(Content content) { + return CmsSwtUtils.cleanPathForUrl(getDataPath(content)); + } + + /** A path in the node repository */ + protected String getDataPath(Content node) { + // TODO make it more configurable? + StringBuilder buf = new StringBuilder(CmsConstants.PATH_API_ACR); + buf.append(node.getPath()); + return buf.toString(); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/ContentComposite.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/ContentComposite.java new file mode 100644 index 000000000..4a35a3bdd --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/ContentComposite.java @@ -0,0 +1,48 @@ +package org.argeo.cms.swt.acr; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.spi.ProvidedContent; +import org.eclipse.swt.widgets.Composite; + +/** A composite which can (optionally) manage a content. */ +public class ContentComposite extends Composite { + private static final long serialVersionUID = -1447009015451153367L; + + public ContentComposite(Composite parent, int style, Content item) { + super(parent, style); + if (item != null) + setData(item); + } + + public boolean hasContent() { + if (getData() == null) + return false; + return getData() instanceof Content; + } + + public Content getContent() { + return (Content) getData(); + } + + @Deprecated + public Content getNode() { + return getContent(); + } + + protected ProvidedContent getProvidedContent() { + return (ProvidedContent) getContent(); + } + + public String getSessionLocalId() { + return getProvidedContent().getSessionLocalId(); + } + + protected void itemUpdated() { + layout(); + } + + public void setContent(Content content) { + setData(content); + itemUpdated(); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java new file mode 100644 index 000000000..eb52fc6a3 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java @@ -0,0 +1,144 @@ +package org.argeo.cms.swt.acr; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.widgets.EditableImage; +import org.argeo.cms.ux.acr.ContentPart; +import org.argeo.eclipse.ui.specific.CmsFileUpload; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** An image within the Argeo Text framework */ +public class Img extends EditableImage implements SwtSectionPart, ContentPart { + private static final long serialVersionUID = 6233572783968188476L; + + private final SwtSection section; + + private final CmsImageManager imageManager; +// private FileUploadHandler currentUploadHandler = null; +// private FileUploadListener fileUploadListener; + + public Img(Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize) { + this(SwtSection.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null); +// setStyle(TextStyles.TEXT_IMAGE); + } + + public Img(Composite parent, int swtStyle, Content imgNode) { + this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, null); +// setStyle(TextStyles.TEXT_IMAGE); + } + + public Img(Composite parent, int swtStyle, Content imgNode, CmsImageManager imageManager) { + this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, imageManager); +// setStyle(TextStyles.TEXT_IMAGE); + } + + Img(SwtSection section, Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize, + CmsImageManager imageManager) { + super(parent, swtStyle, preferredImageSize); + this.section = section; + this.imageManager = imageManager != null ? imageManager + : (CmsImageManager) CmsSwtUtils.getCmsView(section).getImageManager(); +// CmsSwtUtils.style(this, TextStyles.TEXT_IMG); + setData(imgNode); + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + return createImageChooser(box, style); + } else { + return createLabel(box, style); + } + } + + @Override + public synchronized void stopEditing() { + super.stopEditing(); +// fileUploadListener = null; + } + + @Override + protected synchronized Boolean load(Control lbl) { + Content imgNode = getContent(); + boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize()); + // getParent().layout(); + return loaded; + } + + protected Content getUploadFolder() { + return getContent().getParent(); + } + + protected String getUploadName() { + Content node = getContent(); + // TODO centralise pattern? + return NamespaceUtils.toPrefixedName(node.getName()) + '[' + node.getSiblingIndex() + ']'; + } + + protected CmsImageManager getImageManager() { + return imageManager; + } + + protected Control createImageChooser(Composite box, String style) { +// JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(), +// imageManager); +// if (currentUploadHandler != null) +// currentUploadHandler.dispose(); +// currentUploadHandler = prepareUpload(receiver); +// final ServerPushSession pushSession = new ServerPushSession(); + final CmsFileUpload fileUpload = new CmsFileUpload(box, SWT.NONE); + CmsSwtUtils.style(fileUpload, style); + fileUpload.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -9158471843941668562L; + + @Override + public void widgetSelected(SelectionEvent e) { +// pushSession.start(); +// fileUpload.submit(currentUploadHandler.getUploadUrl()); + } + }); + return fileUpload; + } + +// protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) { +// final FileUploadHandler uploadHandler = new FileUploadHandler(receiver); +// if (fileUploadListener != null) +// uploadHandler.addUploadListener(fileUploadListener); +// return uploadHandler; +// } + + @Override + public SwtSection getSection() { + return section; + } + +// public void setFileUploadListener(FileUploadListener fileUploadListener) { +// this.fileUploadListener = fileUploadListener; +// if (currentUploadHandler != null) +// currentUploadHandler.addUploadListener(fileUploadListener); +// } + + @Override + public Content getContent() { + return (Content) getData(); + } + + @Override + public String getPartId() { + return ((ProvidedContent) getContent()).getSessionLocalId(); + } + + @Override + public String toString() { + return "Img #" + getPartId(); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java new file mode 100644 index 000000000..f4f5961ca --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java @@ -0,0 +1,162 @@ +package org.argeo.cms.swt.acr; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.argeo.api.acr.Content; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.widgets.EditablePart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** A structured UI related to an ACR context. */ +public class SwtSection extends ContentComposite { + private static final long serialVersionUID = -5933796173755739207L; + + private final SwtSection parentSection; + private Composite sectionHeader; + private final Integer relativeDepth; + + public SwtSection(Composite parent, int style, Content node) { + this(parent, findSection(parent), style, node); + } + + public SwtSection(SwtSection section, int style, Content node) { + this(section, section, style, node); + } + + public SwtSection(SwtSection section, int style) { + this(section, style, null); + } + + protected SwtSection(Composite parent, SwtSection parentSection, int style, Content node) { + super(parent, style, node); + this.parentSection = parentSection; + if (parentSection != null && hasContent() && parentSection.hasContent()) { + relativeDepth = getProvidedContent().getDepth() - parentSection.getProvidedContent().getDepth(); + } else { + relativeDepth = 0; + } + setLayout(CmsSwtUtils.noSpaceGridLayout()); + } + + public Map getSubSections() { + LinkedHashMap result = new LinkedHashMap(); + for (Control child : getChildren()) { + if (child instanceof Composite) { + collectDirectSubSections((Composite) child, result); + } + } + return Collections.unmodifiableMap(result); + } + + private void collectDirectSubSections(Composite composite, LinkedHashMap subSections) { + if (composite == sectionHeader || composite instanceof EditablePart) + return; + if (composite instanceof SwtSection) { + SwtSection section = (SwtSection) composite; + subSections.put(section.getProvidedContent().getSessionLocalId(), section); + return; + } + + for (Control child : composite.getChildren()) + if (child instanceof Composite) + collectDirectSubSections((Composite) child, subSections); + } + + public Composite createHeader() { + return createHeader(this); + } + + public Composite createHeader(Composite parent) { + if (sectionHeader != null) + sectionHeader.dispose(); + + sectionHeader = new Composite(parent, SWT.NONE); + sectionHeader.setLayoutData(CmsSwtUtils.fillWidth()); + sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout()); + // sectionHeader.moveAbove(null); + // layout(); + return sectionHeader; + } + + public Composite getHeader() { + if (sectionHeader != null && sectionHeader.isDisposed()) + sectionHeader = null; + return sectionHeader; + } + + // SECTION PARTS + public SwtSectionPart getSectionPart(String partId) { + for (Control child : getChildren()) { + if (child instanceof SwtSectionPart) { + SwtSectionPart sectionPart = (SwtSectionPart) child; + if (sectionPart.getPartId().equals(partId)) + return sectionPart; + } + } + return null; + } + + public SwtSectionPart nextSectionPart(SwtSectionPart sectionPart) { + Control[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + if (sectionPart == children[i]) { + for (int j = i + 1; j < children.length; j++) { + if (children[i + 1] instanceof SwtSectionPart) { + return (SwtSectionPart) children[i + 1]; + } + } + +// if (i + 1 < children.length) { +// Composite next = (Composite) children[i + 1]; +// return (SectionPart) next; +// } else { +// // next section +// } + } + } + return null; + } + + public SwtSectionPart previousSectionPart(SwtSectionPart sectionPart) { + Control[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + if (sectionPart == children[i]) + if (i != 0) { + Composite previous = (Composite) children[i - 1]; + return (SwtSectionPart) previous; + } else { + // previous section + } + } + return null; + } + + @Override + public String toString() { + if (parentSection == null) + return "Main section " + getContent(); + return "Section " + getContent(); + } + + public SwtSection getParentSection() { + return parentSection; + } + + public Integer getRelativeDepth() { + return relativeDepth; + } + + /** Recursively finds the related section in the parents (can be itself) */ + public static SwtSection findSection(Control control) { + if (control == null) + return null; + if (control instanceof SwtSection) + return (SwtSection) control; + else + return findSection(control.getParent()); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSectionPart.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSectionPart.java new file mode 100644 index 000000000..7fbf4bbca --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSectionPart.java @@ -0,0 +1,11 @@ +package org.argeo.cms.swt.acr; + +import org.argeo.cms.ux.acr.ContentPart; +import org.argeo.cms.ux.widgets.EditablePart; + +/** An editable part dynamically related to a Section */ +public interface SwtSectionPart extends EditablePart, ContentPart { + public String getPartId(); + + public SwtSection getSection(); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtTabbedArea.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtTabbedArea.java new file mode 100644 index 000000000..b65bc3b6a --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtTabbedArea.java @@ -0,0 +1,265 @@ +package org.argeo.cms.swt.acr; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.Selected; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +/** Manages {@link SwtSection} in a tab-like structure. */ +public class SwtTabbedArea extends Composite { + private static final long serialVersionUID = 8659669229482033444L; + + private Composite headers; + private Composite body; + + private List sections = new ArrayList<>(); + + private ProvidedContent previousNode; + private SwtUiProvider previousUiProvider; + private SwtUiProvider currentUiProvider; + + private String tabStyle; + private String tabSelectedStyle; + private String bodyStyle; + private Image closeIcon; + + private StackLayout stackLayout; + + private boolean singleTab = false; + private String singleTabTitle = null; + + public SwtTabbedArea(Composite parent, int style) { + super(parent, SWT.NONE); + CmsSwtUtils.style(parent, bodyStyle); + + setLayout(CmsSwtUtils.noSpaceGridLayout()); + + // TODO manage tabs at bottom or sides + headers = new Composite(this, SWT.NONE); + headers.setLayoutData(CmsSwtUtils.fillWidth()); + body = new Composite(this, SWT.NONE); + body.setLayoutData(CmsSwtUtils.fillAll()); + // body.setLayout(new FormLayout()); + stackLayout = new StackLayout(); + body.setLayout(stackLayout); + emptyState(); + } + + protected void refreshTabHeaders() { + int tabCount = sections.size() > 0 ? sections.size() : 1; + for (Control tab : headers.getChildren()) + tab.dispose(); + + headers.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(tabCount, true))); + + if (sections.size() == 0) { + Composite emptyHeader = new Composite(headers, SWT.NONE); + emptyHeader.setLayoutData(CmsSwtUtils.fillAll()); + emptyHeader.setLayout(new GridLayout()); + Label lbl = new Label(emptyHeader, SWT.NONE); + lbl.setText(""); + lbl.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false)); + + } + + SwtSection currentSection = getCurrentSection(); + for (SwtSection section : sections) { + boolean selected = section == currentSection; + Composite sectionHeader = section.createHeader(headers); + CmsSwtUtils.style(sectionHeader, selected ? tabSelectedStyle : tabStyle); + int headerColumns = singleTab ? 1 : 2; + sectionHeader.setLayout(new GridLayout(headerColumns, false)); + sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(headerColumns)); + Button title = new Button(sectionHeader, SWT.FLAT); + CmsSwtUtils.style(title, selected ? tabSelectedStyle : tabStyle); + title.setLayoutData(CmsSwtUtils.fillWidth()); + title.addSelectionListener((Selected) (e) -> showTab(tabIndex(section.getContent()))); + Content node = section.getContent(); + + // FIXME find a standard way to display titles + String titleStr = node.getName().getLocalPart(); + if (singleTab && singleTabTitle != null) + titleStr = singleTabTitle; + + // TODO internationalise + title.setText(titleStr); + if (!singleTab) { + ToolBar toolBar = new ToolBar(sectionHeader, SWT.NONE); + ToolItem closeItem = new ToolItem(toolBar, SWT.FLAT); + if (closeIcon != null) + closeItem.setImage(closeIcon); + else + closeItem.setText("X"); + CmsSwtUtils.style(closeItem, selected ? tabSelectedStyle : tabStyle); + closeItem.addSelectionListener((Selected) (e) -> closeTab(section)); + } + } + + } + + public void view(SwtUiProvider uiProvider, Content context) { + if (body.isDisposed()) + return; + int index = tabIndex(context); + if (index >= 0) { + showTab(index); + previousNode = (ProvidedContent) context; + previousUiProvider = uiProvider; + return; + } + SwtSection section = (SwtSection) body.getChildren()[0]; + previousNode = (ProvidedContent) section.getContent(); + if (previousNode == null) {// empty state + previousNode = (ProvidedContent) context; + previousUiProvider = uiProvider; + } else { + previousUiProvider = currentUiProvider; + } + currentUiProvider = uiProvider; + section.setContent(context); + // section.setLayoutData(CmsUiUtils.coverAll()); + build(section, uiProvider, context); + if (sections.size() == 0) + sections.add(section); + refreshTabHeaders(); + index = tabIndex(context); + showTab(index); + layout(true, true); + } + + public void open(SwtUiProvider uiProvider, Content context) { + if (singleTab) + throw new UnsupportedOperationException("Open is not supported in single tab mode."); + + if (previousNode != null + && previousNode.getSessionLocalId().equals(((ProvidedContent) context).getSessionLocalId())) { + // does nothing + return; + } + if (sections.size() == 0) + CmsSwtUtils.clear(body); + SwtSection currentSection = getCurrentSection(); + int currentIndex = sections.indexOf(currentSection); + SwtSection previousSection = new SwtSection(body, SWT.NONE, context); + build(previousSection, previousUiProvider, previousNode); + // previousSection.setLayoutData(CmsUiUtils.coverAll()); + int newIndex = currentIndex + 1; + sections.add(currentIndex, previousSection); +// sections.add(newIndex, previousSection); + showTab(newIndex); + refreshTabHeaders(); + layout(true, true); + } + + public void showTab(int index) { + SwtSection sectionToShow = sections.get(index); + // sectionToShow.moveAbove(null); + stackLayout.topControl = sectionToShow; + refreshTabHeaders(); + layout(true, true); + } + + protected void build(SwtSection section, SwtUiProvider uiProvider, Content context) { + for (Control child : section.getChildren()) + child.dispose(); + CmsSwtUtils.style(section, bodyStyle); + section.setContent(context); + uiProvider.createUiPart(section, context); + + } + + private int tabIndex(Content context) { + for (int i = 0; i < sections.size(); i++) { + SwtSection section = sections.get(i); + if (section.getSessionLocalId().equals(((ProvidedContent) context).getSessionLocalId())) + return i; + } + return -1; + } + + public void closeTab(SwtSection section) { + int currentIndex = sections.indexOf(section); + int nextIndex = currentIndex == 0 ? 0 : currentIndex - 1; + sections.remove(section); + section.dispose(); + if (sections.size() == 0) { + emptyState(); + refreshTabHeaders(); + layout(true, true); + return; + } + refreshTabHeaders(); + showTab(nextIndex); + } + + public void closeAllTabs() { + for (SwtSection section : sections) { + section.dispose(); + } + sections.clear(); + emptyState(); + refreshTabHeaders(); + layout(true, true); + } + + protected void emptyState() { + new SwtSection(body, SWT.NONE, null); + refreshTabHeaders(); + } + + public Composite getCurrent() { + return getCurrentSection(); + } + + protected SwtSection getCurrentSection() { + return (SwtSection) stackLayout.topControl; + } + + public Content getCurrentContext() { + SwtSection section = getCurrentSection(); + if (section != null) { + return section.getContent(); + } else { + return null; + } + } + + public void setTabStyle(String tabStyle) { + this.tabStyle = tabStyle; + } + + public void setTabSelectedStyle(String tabSelectedStyle) { + this.tabSelectedStyle = tabSelectedStyle; + } + + public void setBodyStyle(String bodyStyle) { + this.bodyStyle = bodyStyle; + } + + public void setCloseIcon(Image closeIcon) { + this.closeIcon = closeIcon; + } + + public void setSingleTab(boolean singleTab) { + this.singleTab = singleTab; + } + + public void setSingleTabTitle(String singleTabTitle) { + this.singleTabTitle = singleTabTitle; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtUiProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtUiProvider.java new file mode 100644 index 000000000..4988fc6b8 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtUiProvider.java @@ -0,0 +1,10 @@ +package org.argeo.cms.swt.acr; + +import org.argeo.api.acr.Content; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +@FunctionalInterface +public interface SwtUiProvider { + Control createUiPart(Composite parent, Content context); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/AcrContentTreeView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/AcrContentTreeView.java new file mode 100644 index 000000000..a3d533e2f --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/AcrContentTreeView.java @@ -0,0 +1,166 @@ +package org.argeo.cms.swt.app; + +import static org.argeo.api.acr.NamespaceUtils.toPrefixedName; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.widgets.SwtTableView; +import org.argeo.cms.swt.widgets.SwtTreeView; +import org.argeo.cms.ux.acr.ContentHierarchicalPart; +import org.argeo.cms.ux.widgets.Column; +import org.argeo.cms.ux.widgets.DefaultTabularPart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.widgets.Composite; + +/** A simple ACR browser. */ +public class AcrContentTreeView extends Composite { + private static final long serialVersionUID = -3707881216246077323L; + + private Content rootContent; + +// private Content selected; + + public AcrContentTreeView(Composite parent, int style, Content content) { + super(parent, style); + this.rootContent = content; + // this.selected = rootContent; + setLayout(CmsSwtUtils.noSpaceGridLayout()); + + SashForm split = new SashForm(this, SWT.HORIZONTAL); + split.setLayoutData(CmsSwtUtils.fillAll()); + + ContentHierarchicalPart contentPart = new ContentHierarchicalPart(); + contentPart.addColumn((model) -> { + try { + return NamespaceUtils.toPrefixedName(model.getName()); + } catch (IllegalStateException e) { + return model.getName().toString(); + } + }); + contentPart.setInput(rootContent); + + new SwtTreeView<>(split, getStyle(), contentPart); + + Composite area = new Composite(split, SWT.BORDER); + area.setLayout(CmsSwtUtils.noSpaceGridLayout(2)); + split.setWeights(new int[] { 30, 70 }); + + // attributes + DefaultTabularPart attributesPart = new DefaultTabularPart<>() { + + @Override + protected List asList(Content input) { + return new ArrayList<>(input.keySet()); + } + }; + + attributesPart.addColumn(new Column() { + + @Override + public String getText(QName model) { + try { + return NamespaceUtils.toPrefixedName(model); + } catch (IllegalStateException e) { + return model.toString(); + } + } + }); + attributesPart.addColumn(new Column() { + + @Override + public String getText(QName model) { + return attributesPart.getInput().get(model).toString(); + } + + @Override + public int getWidth() { + return 400; + } + + }); + // attributesPart.setInput(selected); + + SwtTableView attributeTable = new SwtTableView<>(area, style, attributesPart); + attributeTable.setLayoutData(CmsSwtUtils.fillAll()); + + // types + DefaultTabularPart typesPart = new DefaultTabularPart<>() { + + @Override + protected List asList(Content input) { + return input.getContentClasses(); + } + }; + typesPart.addColumn(new Column() { + + @Override + public String getText(QName model) { + return toPrefixedName(model); + } + + }); + + // typesPart.setInput(selected); + + SwtTableView typesTable = new SwtTableView<>(area, style, typesPart); + typesTable.setLayoutData(CmsSwtUtils.fillAll()); + + // controller + contentPart.setInput(rootContent); + contentPart.onSelected((o) -> { + Content c = (Content) o; +// selected = c; + attributesPart.setInput(c); + typesPart.setInput(c); + }); + + attributesPart.refresh(); + typesPart.refresh(); + } + +// protected void refreshTable() { +// for (TableItem item : table.getItems()) { +// item.dispose(); +// } +// for (QName key : selected.keySet()) { +// TableItem item = new TableItem(table, 0); +// item.setText(0, key.toString()); +// Object value = selected.get(key); +// item.setText(1, value.toString()); +// } +// table.getColumn(0).pack(); +// table.getColumn(1).pack(); +// } + +// public static void main(String[] args) { +// Path basePath; +// if (args.length > 0) { +// basePath = Paths.get(args[0]); +// } else { +// basePath = Paths.get(System.getProperty("user.home")); +// } +// +// final Display display = new Display(); +// final Shell shell = new Shell(display); +// shell.setText(basePath.toString()); +// shell.setLayout(new FillLayout()); +// +// FsContentProvider contentSession = new FsContentProvider("/", basePath); +//// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/")); +// +// shell.setSize(shell.computeSize(800, 600)); +// shell.open(); +// while (!shell.isDisposed()) { +// if (!display.readAndDispatch()) +// display.sleep(); +// } +// display.dispose(); +// } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/CmsUserApp.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/CmsUserApp.java new file mode 100644 index 000000000..add6e9edb --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/CmsUserApp.java @@ -0,0 +1,62 @@ +package org.argeo.cms.swt.app; + +import java.util.HashSet; +import java.util.Set; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentRepository; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.ux.CmsUi; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.AbstractCmsApp; +import org.argeo.cms.swt.CmsSwtUi; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.auth.CmsLogin; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +public class CmsUserApp extends AbstractCmsApp { + private ContentRepository contentRepository; + + @Override + public Set getUiNames() { + Set uiNames = new HashSet<>(); + uiNames.add("login"); + uiNames.add("data"); + return uiNames; + } + + @Override + public CmsUi initUi(Object uiParent) { + Composite parent = (Composite) uiParent; + String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null; + CmsSwtUi cmsUi = new CmsSwtUi(parent, SWT.NONE); + if ("login".equals(uiName)) { + CmsView cmsView = CmsSwtUtils.getCmsView(cmsUi); + CmsLogin cmsLogin = new CmsLogin(cmsView, getCmsContext()); + cmsLogin.createUi(cmsUi); + + } else if ("data".equals(uiName)) { + Content rootContent = contentRepository.get().get("/"); + AcrContentTreeView view = new AcrContentTreeView(cmsUi, 0, rootContent); + view.setLayoutData(CmsSwtUtils.fillAll()); + + } + return cmsUi; + } + + @Override + public void refreshUi(CmsUi cmsUi, String state) { + } + + @Override + public void setState(CmsUi cmsUi, String state) { + // TODO Auto-generated method stub + + } + + public void setContentRepository(ContentRepository contentRepository) { + this.contentRepository = contentRepository; + } + +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java new file mode 100644 index 000000000..b27c6f360 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java @@ -0,0 +1,339 @@ +package org.argeo.cms.swt.auth; + +import static org.argeo.cms.CmsMsg.password; +import static org.argeo.cms.CmsMsg.username; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.LanguageCallback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.LocaleUtils; +import org.argeo.cms.auth.RemoteAuthCallback; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; +import org.argeo.cms.swt.CmsStyles; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.eclipse.ui.specific.UiContext; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class CmsLogin implements CmsStyles, CallbackHandler { + private final static CmsLog log = CmsLog.getLog(CmsLogin.class); + + private Composite parent; + private Text usernameT, passwordT; + private Composite credentialsBlock; + private final SelectionListener loginSelectionListener; + + private final Locale defaultLocale; + private LocaleChoice localeChoice = null; + + private final CmsView cmsView; + + // optional subject to be set explicitly + private Subject subject = null; + + private CmsContext cmsContext; + + public CmsLogin(CmsView cmsView, CmsContext cmsContext) { + this.cmsView = cmsView; + this.cmsContext = cmsContext; + if (this.cmsContext != null) { + defaultLocale = this.cmsContext.getDefaultLocale(); + List locales = this.cmsContext.getLocales(); + if (locales != null && locales.size() > 1) + localeChoice = new LocaleChoice(locales, defaultLocale); + } else { + defaultLocale = Locale.getDefault(); + } + loginSelectionListener = new SelectionListener() { + private static final long serialVersionUID = -8832133363830973578L; + + @Override + public void widgetSelected(SelectionEvent e) { + login(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }; + } + + protected boolean isAnonymous() { + return cmsView.isAnonymous(); + } + + public final void createUi(Composite parent) { + this.parent = parent; + createContents(parent); + } + + protected void createContents(Composite parent) { + defaultCreateContents(parent); + } + + public final void defaultCreateContents(Composite parent) { + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + Composite credentialsBlock = createCredentialsBlock(parent); + if (parent instanceof Shell) { + credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + } + } + + public final Composite createCredentialsBlock(Composite parent) { + if (isAnonymous()) { + return anonymousUi(parent); + } else { + return userUi(parent); + } + } + + public Composite getCredentialsBlock() { + return credentialsBlock; + } + + protected Composite userUi(Composite parent) { + Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); + credentialsBlock = new Composite(parent, SWT.NONE); + credentialsBlock.setLayout(new GridLayout()); + // credentialsBlock.setLayoutData(CmsUiUtils.fillAll()); + + specificUserUi(credentialsBlock); + + Label l = new Label(credentialsBlock, SWT.NONE); + CmsSwtUtils.style(l, CMS_USER_MENU_ITEM); + l.setText(CmsMsg.logout.lead(locale)); + GridData lData = CmsSwtUtils.fillWidth(); + lData.widthHint = 120; + l.setLayoutData(lData); + + l.addMouseListener(new MouseAdapter() { + private static final long serialVersionUID = 6444395812777413116L; + + public void mouseDown(MouseEvent e) { + logout(); + } + }); + return credentialsBlock; + } + + /** To be overridden */ + protected void specificUserUi(Composite parent) { + + } + + protected Composite anonymousUi(Composite parent) { + Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); + // We need a composite for the traversal + credentialsBlock = new Composite(parent, SWT.NONE); + credentialsBlock.setLayout(new GridLayout()); + // credentialsBlock.setLayoutData(CmsUiUtils.fillAll()); + CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG); + + Integer textWidth = 200; + if (parent instanceof Shell) + CmsSwtUtils.style(parent, CMS_USER_MENU); + // new Label(this, SWT.NONE).setText(CmsMsg.username.lead()); + usernameT = new Text(credentialsBlock, SWT.BORDER); + usernameT.setMessage(username.lead(locale)); + CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME); + GridData gd = CmsSwtUtils.fillWidth(); + gd.widthHint = textWidth; + usernameT.setLayoutData(gd); + + // new Label(this, SWT.NONE).setText(CmsMsg.password.lead()); + passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD); + passwordT.setMessage(password.lead(locale)); + CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD); + gd = CmsSwtUtils.fillWidth(); + gd.widthHint = textWidth; + passwordT.setLayoutData(gd); + + TraverseListener tl = new TraverseListener() { + private static final long serialVersionUID = -1158892811534971856L; + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) + login(); + } + }; + credentialsBlock.addTraverseListener(tl); + usernameT.addTraverseListener(tl); + passwordT.addTraverseListener(tl); + parent.setTabList(new Control[] { credentialsBlock }); + credentialsBlock.setTabList(new Control[] { usernameT, passwordT }); + + // Button + Button loginButton = new Button(credentialsBlock, SWT.PUSH); + loginButton.setText(CmsMsg.login.lead(locale)); + loginButton.setLayoutData(CmsSwtUtils.fillWidth()); + loginButton.addSelectionListener(loginSelectionListener); + + extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener); + if (localeChoice != null) + createLocalesBlock(credentialsBlock); + return credentialsBlock; + } + + /** + * To be overridden in order to provide custom login button and other links. + */ + protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale, + SelectionListener loginSelectionListener) { + + } + + protected void updateLocale(Locale selectedLocale) { + // save already entered values + String usernameStr = usernameT.getText(); + char[] pwd = passwordT.getTextChars(); + + for (Control child : parent.getChildren()) + child.dispose(); + createContents(parent); + if (parent.getParent() != null) + parent.getParent().layout(true, true); + else + parent.layout(); + usernameT.setText(usernameStr); + passwordT.setTextChars(pwd); + } + + protected Composite createLocalesBlock(final Composite parent) { + Composite c = new Composite(parent, SWT.NONE); + CmsSwtUtils.style(c, CMS_USER_MENU_ITEM); + c.setLayout(CmsSwtUtils.noSpaceGridLayout()); + c.setLayoutData(CmsSwtUtils.fillAll()); + + SelectionListener selectionListener = new SelectionAdapter() { + private static final long serialVersionUID = 4891637813567806762L; + + public void widgetSelected(SelectionEvent event) { + Button button = (Button) event.widget; + if (button.getSelection()) { + localeChoice.setSelectedIndex((Integer) event.widget.getData()); + updateLocale(localeChoice.getSelectedLocale()); + } + }; + }; + + List locales = localeChoice.getLocales(); + for (Integer i = 0; i < locales.size(); i++) { + Locale locale = locales.get(i); + Button button = new Button(c, SWT.RADIO); + CmsSwtUtils.style(button, CMS_USER_MENU_ITEM); + button.setData(i); + button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")"); + // button.addListener(SWT.Selection, listener); + button.addSelectionListener(selectionListener); + if (i == localeChoice.getSelectedIndex()) + button.setSelection(true); + } + return c; + } + + protected boolean login() { + // TODO use CmsVie in order to retrieve subject? + // Subject subject = cmsView.getLoginContext().getSubject(); + // LoginContext loginContext = cmsView.getLoginContext(); + try { + // + // LOGIN + // + // loginContext.logout(); + LoginContext loginContext; + if (subject == null) + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this); + else + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this); + loginContext.login(); + cmsView.authChange(loginContext); + cmsContext.getCmsEventBus().sendEvent("cms", Collections.singletonMap("msg", "New login")); + return true; + } catch (LoginException e) { + if (log.isTraceEnabled()) + log.warn("Login failed: " + e.getMessage(), e); + else + log.warn("Login failed: " + e.getMessage()); + + try { + Thread.sleep(3000); + } catch (InterruptedException e2) { + // silent + } + // ErrorFeedback.show("Login failed", e); + return false; + } + // catch (LoginException e) { + // log.error("Cannot login", e); + // return false; + // } + } + + protected void logout() { + cmsView.logout(); + cmsView.navigateTo("~"); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback && usernameT != null) + ((NameCallback) callback).setName(usernameT.getText()); + else if (callback instanceof PasswordCallback && passwordT != null) + ((PasswordCallback) callback).setPassword(passwordT.getTextChars()); + else if (callback instanceof RemoteAuthCallback) { + ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); + ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); + } else if (callback instanceof LanguageCallback) { + Locale toUse = null; + if (localeChoice != null) + toUse = localeChoice.getSelectedLocale(); + else if (defaultLocale != null) + toUse = defaultLocale; + + if (toUse != null) { + ((LanguageCallback) callback).setLocale(toUse); + UiContext.setLocale(toUse); + } + + } + } + } + + public void setSubject(Subject subject) { + this.subject = subject; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java new file mode 100644 index 000000000..39cf82afc --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java @@ -0,0 +1,73 @@ +package org.argeo.cms.swt.auth; + +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** The site-related user menu */ +public class CmsLoginShell extends CmsLogin { + private final Shell shell; + + public CmsLoginShell(CmsView cmsView, CmsContext cmsContext) { + super(cmsView, cmsContext); + shell = createShell(); +// createUi(shell); + } + + /** To be overridden. */ + protected Shell createShell() { + Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM); + shell.setMaximized(true); + return shell; + } + + /** To be overridden. */ + public void open() { + CmsSwtUtils.style(shell, CMS_USER_MENU); + shell.open(); + } + + @Override + protected boolean login() { + boolean success = false; + try { + success = super.login(); + return success; + } finally { + if (success) + closeShell(); + else { + for (Control child : shell.getChildren()) + child.dispose(); + createUi(shell); + shell.layout(); + // TODO error message + } + } + } + + @Override + protected void logout() { + closeShell(); + super.logout(); + } + + protected void closeShell() { + if (!shell.isDisposed()) { + shell.close(); + shell.dispose(); + } + } + + public Shell getShell() { + return shell; + } + + public void createUi() { + createUi(shell); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java new file mode 100644 index 000000000..495007cb2 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java @@ -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:
    + *
      + *
    • {@link PasswordCallback}
    • + *
    • {@link NameCallback}
    • + *
    • {@link TextOutputCallback}
    • + *
    + * Supported Argeo {@link Callback}s are:
    + *
      + *
    • {@link LocaleChoice}
    • + *
    + */ +public class CompositeCallbackHandler extends Composite implements CallbackHandler { + private static final long serialVersionUID = -928223893722723777L; + + private boolean wasUsedAlready = false; + private boolean isSubmitted = false; + private boolean isCanceled = false; + + public CompositeCallbackHandler(Composite parent, int style) { + super(parent, style); + } + + @Override + public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { + // reset + if (wasUsedAlready && !isSubmitted() && !isCanceled()) { + cancel(); + for (Control control : getChildren()) + control.dispose(); + isSubmitted = false; + isCanceled = false; + } + + for (Callback callback : callbacks) + checkCallbackSupported(callback); + // create controls synchronously in the UI thread + getDisplay().syncExec(new Runnable() { + + @Override + public void run() { + createCallbackHandlers(callbacks); + } + }); + + if (!wasUsedAlready) + wasUsedAlready = true; + + // while (!isSubmitted() && !isCanceled()) { + // try { + // wait(1000l); + // } catch (InterruptedException e) { + // // silent + // } + // } + + // cleanCallbacksAfterCancel(callbacks); + } + + public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException { + if (callback instanceof TextOutputCallback || callback instanceof NameCallback + || callback instanceof PasswordCallback || callback instanceof LocaleChoice) { + return; + } else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Set writable callbacks to null if the handle is canceled (check is done + * by the method) + */ + public void cleanCallbacksAfterCancel(Callback[] callbacks) { + if (isCanceled()) { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(null); + } else if (callback instanceof PasswordCallback) { + PasswordCallback pCallback = (PasswordCallback) callback; + char[] arr = pCallback.getPassword(); + if (arr != null) { + Arrays.fill(arr, '*'); + pCallback.setPassword(null); + } + } + } + } + } + + public void createCallbackHandlers(Callback[] callbacks) { + Composite composite = this; + for (int i = 0; i < callbacks.length; i++) { + Callback callback = callbacks[i]; + if (callback instanceof TextOutputCallback) { + createLabelTextoutputHandler(composite, (TextOutputCallback) callback); + } else if (callback instanceof NameCallback) { + createNameHandler(composite, (NameCallback) callback); + } else if (callback instanceof PasswordCallback) { + createPasswordHandler(composite, (PasswordCallback) callback); + } else if (callback instanceof LocaleChoice) { + createLocaleHandler(composite, (LocaleChoice) callback); + } + } + } + + protected Text createNameHandler(Composite composite, final NameCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER); + if (callback.getDefaultName() != null) { + // set default value, if provided + text.setText(callback.getDefaultName()); + callback.setName(callback.getDefaultName()); + } + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7300032545287292973L; + + public void modifyText(ModifyEvent event) { + callback.setName(text.getText()); + } + }); + text.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + + text.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8698107785092095713L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + } + }); + return text; + } + + protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); + passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + passwordText.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -7099363995047686732L; + + public void modifyText(ModifyEvent event) { + callback.setPassword(passwordText.getTextChars()); + } + }); + passwordText.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + return passwordText; + } + + protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) { + String[] labels = callback.getSupportedLocalesLabels(); + if (labels.length == 0) + return null; + Label label = new Label(composite, SWT.NONE); + label.setText("Language"); + + final Combo combo = new Combo(composite, SWT.READ_ONLY); + combo.setItems(labels); + combo.select(callback.getDefaultIndex()); + combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + combo.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 38678989091946277L; + + @Override + public void widgetSelected(SelectionEvent e) { + callback.setSelectedIndex(combo.getSelectionIndex()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + return combo; + } + + protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getMessage()); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + data.horizontalSpan = 2; + label.setLayoutData(data); + return label; + // TODO: find a way to pass this information + // int messageType = callback.getMessageType(); + // int dialogMessageType = IMessageProvider.NONE; + // switch (messageType) { + // case TextOutputCallback.INFORMATION: + // dialogMessageType = IMessageProvider.INFORMATION; + // break; + // case TextOutputCallback.WARNING: + // dialogMessageType = IMessageProvider.WARNING; + // break; + // case TextOutputCallback.ERROR: + // dialogMessageType = IMessageProvider.ERROR; + // break; + // } + // setMessage(callback.getMessage(), dialogMessageType); + } + + synchronized boolean isSubmitted() { + return isSubmitted; + } + + synchronized boolean isCanceled() { + return isCanceled; + } + + protected synchronized void submit() { + isSubmitted = true; + notifyAll(); + } + + protected synchronized void cancel() { + isCanceled = true; + notifyAll(); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java new file mode 100644 index 000000000..37d88f5c3 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java @@ -0,0 +1,34 @@ +package org.argeo.cms.swt.auth; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.argeo.cms.swt.dialogs.LightweightDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +public class DynamicCallbackHandler implements CallbackHandler { + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + Shell activeShell = Display.getCurrent().getActiveShell(); + LightweightDialog dialog = new LightweightDialog(activeShell) { + + @Override + protected Control createDialogArea(Composite parent) { + CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE); + cch.createCallbackHandlers(callbacks); + return cch; + } + }; + dialog.setBlockOnOpen(true); + dialog.open(); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java new file mode 100644 index 000000000..3ce5ae516 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java @@ -0,0 +1,86 @@ +package org.argeo.cms.swt.auth; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.callback.LanguageCallback; + +import org.argeo.cms.swt.CmsException; + +/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ +@Deprecated +public class LocaleChoice { + private final List locales; + + private Integer selectedIndex = null; + private final Integer defaultIndex; + + public LocaleChoice(List 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 getLocales() { + return locales; + } + + public Locale getDefaultLocale() { + return locales.get(getDefaultIndex()); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java new file mode 100644 index 000000000..b431423d8 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS authentication widgets, based on SWT. */ +package org.argeo.cms.swt.auth; \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java new file mode 100644 index 000000000..abb8227cd --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java @@ -0,0 +1,84 @@ +package org.argeo.cms.swt.dialogs; + +import java.util.Arrays; +import java.util.concurrent.Callable; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.directory.CmsUserManager; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** Dialog to change a password. */ +public class ChangePasswordDialog extends CmsMessageDialog { + private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class); + + private CmsUserManager cmsUserManager; + private CmsView cmsView; + + private Callable doIt; + + public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) { + super(parentShell, message, kind); + this.cmsUserManager = cmsUserManager; + cmsView = CmsSwtUtils.getCmsView(parentShell); + } + + @Override + protected Control createInputArea(Composite userSection) { + addFormLabel(userSection, CmsMsg.currentPassword.lead()); + Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + previousPassword.setLayoutData(CmsSwtUtils.fillWidth()); + addFormLabel(userSection, CmsMsg.newPassword.lead()); + Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + newPassword.setLayoutData(CmsSwtUtils.fillWidth()); + addFormLabel(userSection, CmsMsg.repeatNewPassword.lead()); + Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + confirmPassword.setLayoutData(CmsSwtUtils.fillWidth()); + + doIt = () -> { + if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) { + try { + cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars()); + return CmsDialog.OK; + } catch (Exception e1) { + log.error("Could not change password", e1); + cancel(); + CmsMessageDialog.openError(CmsMsg.invalidPassword.lead()); + return CmsDialog.CANCEL; + } + } else { + cancel(); + CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead()); + return CmsDialog.CANCEL; + } + }; + + pack(); + return previousPassword; + } + + @Override + protected void okPressed() { + Integer returnCode = cmsView.doAs(doIt); + if (returnCode.equals(CmsDialog.OK)) { + super.okPressed(); + CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead()); + } + } + + private static Label addFormLabel(Composite parent, String label) { + Label lbl = new Label(parent, SWT.WRAP); + lbl.setText(label); +// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel); + return lbl; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java new file mode 100644 index 000000000..2fed95199 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java @@ -0,0 +1,127 @@ +package org.argeo.cms.swt.dialogs; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.Selected; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** A dialog feedback based on a {@link LightweightDialog}. */ +public class CmsFeedback extends LightweightDialog { + private final static CmsLog log = CmsLog.getLog(CmsFeedback.class); + + private String message; + private Throwable exception; + + private Text stack; + + private CmsFeedback(Shell parentShell, String message, Throwable e) { + super(parentShell); + this.message = message; + this.exception = e; + } + + public static CmsFeedback error(String message, Throwable e) { + // rethrow ThreaDeath in order to make sure that RAP will properly clean + // up the UI thread + if (e instanceof ThreadDeath) + throw (ThreadDeath) e; + + log.error(message, e); + try { + Display display = LightweightDialog.findDisplay(); + + CmsFeedback current = (CmsFeedback) display.getData(CmsFeedback.class.getName()); + if (current != null) {// already one open + current.append(""); + if (message != null) + current.append(message); + if (e != null) + current.append(e); + // FIXME set a limit to the size of the text + return current; + } + + CmsFeedback cmsFeedback = new CmsFeedback(null, message, e); + cmsFeedback.setBlockOnOpen(false); + cmsFeedback.open(); + cmsFeedback.getDisplay().setData(CmsFeedback.class.getName(), cmsFeedback); + return cmsFeedback; + } catch (Throwable e1) { + log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e); + return null; + } + } + + public static CmsFeedback show(String message) { + CmsFeedback cmsFeedback = new CmsFeedback(null, message, null); + cmsFeedback.open(); + return cmsFeedback; + } + + protected Control createDialogArea(Composite parent) { + parent.setLayout(new GridLayout(2, false)); + + Label messageLbl = new Label(parent, SWT.WRAP); + messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + if (message != null) + messageLbl.setText(message); + else if (exception != null) + messageLbl.setText(exception.getLocalizedMessage()); + + Button close = new Button(parent, SWT.FLAT); + close.setText(CmsMsg.close.lead()); + close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false)); + close.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.OK)); + + if (exception != null) { + stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + stack.setEditable(false); + stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + append(exception); + } + return messageLbl; + } + + protected Point getInitialSize() { + if (exception != null) + return new Point(800, 600); + else + return new Point(600, 400); + } + + protected void append(String message) { + stack.append(message); + stack.append("\n"); + } + + protected void append(Throwable exception) { + try (StringWriter sw = new StringWriter()) { + exception.printStackTrace(new PrintWriter(sw)); + stack.append(sw.toString()); + } catch (IOException e) { + // ignore + } + + } + + @Override + protected void onClose() { + getDisplay().setData(CmsFeedback.class.getName(), null); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java new file mode 100644 index 000000000..21308824d --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java @@ -0,0 +1,168 @@ +package org.argeo.cms.swt.dialogs; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.Selected; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** Base class for dialogs displaying messages or small forms. */ +public class CmsMessageDialog extends LightweightDialog { + public final static int NONE = 0; + public final static int ERROR = 1; + public final static int INFORMATION = 2; + public final static int QUESTION = 3; + public final static int WARNING = 4; + public final static int CONFIRM = 5; + public final static int QUESTION_WITH_CANCEL = 6; + + private int kind; + private String message; + + public CmsMessageDialog(Shell parentShell, String message, int kind) { + super(parentShell); + this.kind = kind; + this.message = message; + } + + protected Control createDialogArea(Composite parent) { + parent.setLayout(new GridLayout()); + + TraverseListener traverseListener = new TraverseListener() { + private static final long serialVersionUID = -1158892811534971856L; + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) + okPressed(); + else if (e.detail == SWT.TRAVERSE_ESCAPE) + cancelPressed(); + } + }; + + // message + Composite body = new Composite(parent, SWT.NONE); + body.addTraverseListener(traverseListener); + GridLayout bodyGridLayout = new GridLayout(); + bodyGridLayout.marginHeight = 20; + bodyGridLayout.marginWidth = 20; + body.setLayout(bodyGridLayout); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + if (message != null) { + Label messageLbl = new Label(body, SWT.WRAP); + CmsSwtUtils.markup(messageLbl); + messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + messageLbl.setFont(EclipseUiUtils.getBoldFont(parent)); + messageLbl.setText(message); + } + + // buttons + Composite buttons = new Composite(parent, SWT.NONE); + buttons.addTraverseListener(traverseListener); + buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) { + GridLayout layout = new GridLayout(1, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + + Button close = new Button(buttons, SWT.FLAT); + close.setText(CmsMsg.close.lead()); + close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + close.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.OK)); + close.setFocus(); + close.addTraverseListener(traverseListener); + + buttons.setTabList(new Control[] { close }); + } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) { + Control input = createInputArea(body); + if (input != null) { + input.addTraverseListener(traverseListener); + body.setTabList(new Control[] { input }); + } + GridLayout layout = new GridLayout(2, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + + Button cancel = new Button(buttons, SWT.FLAT); + cancel.setText(CmsMsg.cancel.lead()); + cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + cancel.addSelectionListener((Selected) (e) -> cancelPressed()); + cancel.addTraverseListener(traverseListener); + + Button ok = new Button(buttons, SWT.FLAT); + ok.setText(CmsMsg.ok.lead()); + ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + ok.addSelectionListener((Selected) (e) -> okPressed()); + ok.addTraverseListener(traverseListener); + if (input == null) + ok.setFocus(); + else + input.setFocus(); + + buttons.setTabList(new Control[] { ok, cancel }); + } + // pack(); + parent.setTabList(new Control[] { body, buttons }); + return body; + } + + protected Control createInputArea(Composite parent) { + return null; + } + + protected void okPressed() { + closeShell(CmsDialog.OK); + } + + protected void cancelPressed() { + closeShell(CmsDialog.CANCEL); + } + + protected void cancel() { + closeShell(CmsDialog.CANCEL); + } + + protected Point getInitialSize() { + return new Point(400, 200); + } + + public static boolean open(int kind, Shell parent, String message) { + CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind); + return dialog.open() == 0; + } + + public static boolean openConfirm(String message) { + return open(CONFIRM, Display.getCurrent().getActiveShell(), message); + } + + public static void openInformation(String message) { + open(INFORMATION, Display.getCurrent().getActiveShell(), message); + } + + public static boolean openQuestion(String message) { + return open(QUESTION, Display.getCurrent().getActiveShell(), message); + } + + public static void openWarning(String message) { + open(WARNING, Display.getCurrent().getActiveShell(), message); + } + + public static void openError(String message) { + open(ERROR, Display.getCurrent().getActiveShell(), message); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java new file mode 100644 index 000000000..3aec22a14 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java @@ -0,0 +1,260 @@ +package org.argeo.cms.swt.dialogs; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** Generic lightweight dialog, not based on JFace. */ +public class LightweightDialog { + private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); + + private Shell parentShell; + private Shell backgroundShell; + private Shell foregoundShell; + + private Display display; + + private Integer returnCode = null; + private boolean block = true; + + private String title; + + /** Tries to find a display */ + static Display findDisplay() { + try { + Display display = Display.getCurrent(); + if (display != null) + return display; + else + return Display.getDefault(); + } catch (Exception e) { + return Display.getCurrent(); + } + } + + public LightweightDialog(Shell parentShell) { + this.parentShell = parentShell; + } + + public int open() { + display = findDisplay(); + if (foregoundShell != null) + throw new EclipseUiException("There is already a shell"); + backgroundShell = new Shell(parentShell, SWT.ON_TOP); + backgroundShell.setFullScreen(true); + // if (parentShell != null) { + // backgroundShell.setBounds(parentShell.getBounds()); + // } else + // backgroundShell.setMaximized(true); + backgroundShell.setAlpha(128); + backgroundShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); + if (title != null) + setTitle(title); + foregoundShell.setLayout(new GridLayout()); + foregoundShell.setSize(getInitialSize()); + createDialogArea(foregoundShell); + // shell.pack(); + // shell.layout(); + + Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : display.getBounds();// RAP + Point dialogSize = foregoundShell.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + foregoundShell.setLocation(x, y); + + foregoundShell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = -2701270481953688763L; + + @Override + public void shellDeactivated(ShellEvent e) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CmsDialog.CANCEL); + } + + @Override + public void shellClosed(ShellEvent e) { + notifyClose(); + } + + }); + + backgroundShell.open(); + foregoundShell.open(); + // after the foreground shell has been opened + backgroundShell.addFocusListener(new FocusListener() { + private static final long serialVersionUID = 3137408447474661070L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CmsDialog.CANCEL); + } + }); + backgroundShell.addDisposeListener((event) -> onClose()); + + if (block) { + block(); + } + if (returnCode == null) + returnCode = CmsDialog.OK; + return returnCode; + } + + public void block() { + try { + runEventLoop(foregoundShell); + } catch (ThreadDeath t) { + returnCode = CmsDialog.CANCEL; + if (log.isTraceEnabled()) + log.error("Thread death, canceling dialog", t); + } catch (Throwable t) { + returnCode = CmsDialog.CANCEL; + log.error("Cannot open blocking lightweight dialog", t); + } + } + + private boolean hasChildShells() { + if (foregoundShell == null) + return false; + return foregoundShell.getShells().length != 0; + } + + protected void onClose() { + + } + + // public synchronized int openAndWait() { + // open(); + // while (returnCode == null) + // try { + // wait(100); + // } catch (InterruptedException e) { + // // silent + // } + // return returnCode; + // } + + private synchronized void notifyClose() { + if (returnCode == null) + returnCode = CmsDialog.CANCEL; + notifyAll(); + } + + protected void closeShell(int returnCode) { + this.returnCode = returnCode; + if (CmsDialog.CANCEL == returnCode) + onCancel(); + if (foregoundShell != null && !foregoundShell.isDisposed()) { + foregoundShell.close(); + foregoundShell.dispose(); + foregoundShell = null; + } + + if (backgroundShell != null && !backgroundShell.isDisposed()) { + backgroundShell.close(); + backgroundShell.dispose(); + } + } + + protected Point getInitialSize() { + return new Point(600, 400); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = new Composite(parent, SWT.NONE); + dialogarea.setLayout(new GridLayout()); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return dialogarea; + } + + protected Shell getBackgroundShell() { + return backgroundShell; + } + + protected Shell getForegoundShell() { + return foregoundShell; + } + + public void setBlockOnOpen(boolean shouldBlock) { + block = shouldBlock; + } + + public void pack() { + foregoundShell.pack(); + } + + private void runEventLoop(Shell loopShell) { + Display display; + if (foregoundShell == null) { + display = Display.getCurrent(); + } else { + display = loopShell.getDisplay(); + } + + while (loopShell != null && !loopShell.isDisposed()) { + try { + if (!display.readAndDispatch()) { + display.sleep(); + } + } catch (UnsupportedOperationException e) { + throw e; + } catch (Throwable e) { + handleException(e); + } + } + if (!display.isDisposed()) + display.update(); + } + + protected void handleException(Throwable t) { + if (t instanceof ThreadDeath) { + // Don't catch ThreadDeath as this is a normal occurrence when + // the thread dies + throw (ThreadDeath) t; + } + // Try to keep running. + t.printStackTrace(); + } + + /** @return false, if the dialog should not be closed. */ + protected boolean onCancel() { + return true; + } + + public void setTitle(String title) { + this.title = title; + if (title != null && getForegoundShell() != null) + getForegoundShell().setText(title); + } + + public Integer getReturnCode() { + return returnCode; + } + + Display getDisplay() { + return display; + } + +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java new file mode 100644 index 000000000..9404b81da --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java @@ -0,0 +1,82 @@ +package org.argeo.cms.swt.dialogs; + +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** A dialog asking a for a single value. */ +public class SingleValueDialog extends CmsMessageDialog { + private Text valueT; + private String value; + private String defaultValue; + + public SingleValueDialog(Shell parentShell, String message) { + super(parentShell, message, QUESTION); + } + + public SingleValueDialog(Shell parentShell, String message, String defaultValue) { + super(parentShell, message, QUESTION); + this.defaultValue = defaultValue; + } + + @Override + protected Control createInputArea(Composite parent) { + valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); + valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); + if (defaultValue != null) + valueT.setText(defaultValue); + return valueT; + } + + @Override + protected void okPressed() { + value = valueT.getText(); + super.okPressed(); + } + + public String getString() { + return value; + } + + public Long getLong() { + return Long.valueOf(getString()); + } + + public Double getDouble() { + return Double.valueOf(getString()); + } + + public static String ask(String message) { + return ask(message, null); + } + + public static String ask(String message, String defaultValue) { + SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue); + if (svd.open() == Window.OK) + return svd.getString(); + else + return null; + } + + public static Long askLong(String message) { + SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); + if (svd.open() == Window.OK) + return svd.getLong(); + else + return null; + } + + public static Double askDouble(String message) { + SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); + if (svd.open() == Window.OK) + return svd.getDouble(); + else + return null; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java new file mode 100644 index 000000000..ac76dba81 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java @@ -0,0 +1,2 @@ +/** SWT/JFace dialogs. */ +package org.argeo.cms.swt.dialogs; \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java new file mode 100644 index 000000000..b3fec78ec --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java @@ -0,0 +1,111 @@ +package org.argeo.cms.swt.osgi; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.argeo.api.cms.ux.CmsIcon; +import org.argeo.cms.osgi.BundleCmsTheme; +import org.argeo.cms.swt.CmsSwtTheme; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; + +/** Centralises some generic {@link CmsSwtTheme} patterns. */ +public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme { + private Map imageCache = new HashMap<>(); + + private Map> iconPaths = new HashMap<>(); + + protected Image getImage(String path) { + if (!imageCache.containsKey(path)) { + try (InputStream in = getResourceAsStream(path)) { + if (in == null) + return null; + ImageData imageData = new ImageData(in); + imageCache.put(path, imageData); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + ImageData imageData = imageCache.get(path); + Image image = new Image(Display.getCurrent(), imageData); + return image; + } + + /** + * And icon with this file name (without the extension), with a best effort to + * find the appropriate size, or null if not found. + * + * @param name An icon file name without path and extension. + * @param preferredSize the preferred size, if null, + * {@link #getSmallIconSize()} will be tried. + */ + public Image getIcon(String name, Integer preferredSize) { + if (preferredSize == null) + preferredSize = getSmallIconSize(); + Map subCache; + if (!iconPaths.containsKey(name)) + subCache = new HashMap<>(); + else + subCache = iconPaths.get(name); + Image image = null; + if (!subCache.containsKey(preferredSize)) { + Image bestMatchSoFar = null; + paths: for (String p : getImagesPaths()) { + int lastSlash = p.lastIndexOf('/'); + String fileName = p; + String ext = ""; + if (lastSlash >= 0) + fileName = p.substring(lastSlash + 1); + int lastDot = fileName.lastIndexOf('.'); + if (lastDot >= 0) { + ext = fileName.substring(lastDot + 1); + fileName = fileName.substring(0, lastDot); + } + + if ("svg".equals(ext)) + continue paths; + + if (fileName.equals(name)) {// matched + Image img = getImage(p); + int width = img.getBounds().width; + if (width == preferredSize) {// perfect match + subCache.put(preferredSize, p); + image = img; + break paths; + } + if (bestMatchSoFar == null) { + bestMatchSoFar = img; + } else { + if (Math.abs(width - preferredSize) < Math + .abs(bestMatchSoFar.getBounds().width - preferredSize)) + bestMatchSoFar = img; + } + } + } + + if (image == null) + image = bestMatchSoFar; + } else { + image = getImage(subCache.get(preferredSize)); + } + + if (image != null && !iconPaths.containsKey(name)) + iconPaths.put(name, subCache); + + return image; + } + + @Override + public Image getSmallIcon(CmsIcon icon) { + return getIcon(icon.name(), getSmallIconSize()); + } + + @Override + public Image getBigIcon(CmsIcon icon) { + return getIcon(icon.name(), getBigIconSize()); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java new file mode 100644 index 000000000..1e52001cb --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java @@ -0,0 +1,107 @@ +package org.argeo.cms.swt.osgi; + +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.batik.transcoder.TranscoderException; +import org.apache.batik.transcoder.TranscoderInput; +import org.apache.batik.transcoder.TranscoderOutput; +import org.apache.batik.transcoder.image.ImageTranscoder; +import org.apache.batik.transcoder.image.PNGTranscoder; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.BundleContext; + +/** Theme which can dynamically create icons from SVG data. */ +public class BundleSvgTheme extends BundleCmsSwtTheme { + private final static Logger logger = System.getLogger(BundleSvgTheme.class.getName()); + + private Map> imageCache = Collections.synchronizedMap(new HashMap<>()); + + private Map transcoders = Collections.synchronizedMap(new HashMap<>()); + + @Override + public Image getIcon(String name, Integer preferredSize) { + String path = "icons/types/svg/" + name + ".svg"; + return createImageFromSvg(path, preferredSize); + } + + protected Image createImageFromSvg(String path, Integer preferredSize) { + Image image = null; + if (imageCache.containsKey(path)) { + image = imageCache.get(path).get(preferredSize); + } + if (image != null) + return image; + ImageData imageData = loadFromSvg(path, preferredSize); + image = new Image(Display.getDefault(), imageData); + if (!imageCache.containsKey(path)) + imageCache.put(path, Collections.synchronizedMap(new HashMap<>())); + imageCache.get(path).put(preferredSize, image); + return image; + } + + protected ImageData loadFromSvg(String path, int size) { + ImageTranscoder transcoder = null; + synchronized (this) { + transcoder = transcoders.get(size); + if (transcoder == null) { + transcoder = new PNGTranscoder(); + transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) size); + transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) size); + transcoder.addTranscodingHint(PNGTranscoder.KEY_BACKGROUND_COLOR, new Color(255, 255, 255, 0)); + transcoders.put(size, transcoder); + } + } + ImageData imageData; + try (InputStream in = getResourceAsStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + if (in == null) + throw new IllegalArgumentException(path + " not found"); + TranscoderInput input = new TranscoderInput(in); + TranscoderOutput output = new TranscoderOutput(out); + transcoder.transcode(input, output); + try (InputStream imageIn = new ByteArrayInputStream(out.toByteArray())) { + imageData = new ImageData(imageIn); + } + logger.log(Level.DEBUG, () -> "Generated " + size + "x" + size + " PNG icon from " + path); + } catch (IOException | TranscoderException e) { + throw new RuntimeException("Cannot transcode SVG " + path, e); + } + + return imageData; + } + + @Override + public void init(BundleContext bundleContext, Map properties) { + super.init(bundleContext, properties); + + // preload all icons +// paths: for (String p : getImagesPaths()) { +// if (!p.endsWith(".svg")) +// continue paths; +// createImageFromSvg(p, getDefaultIconSize()); +// } + } + + @Override + public void destroy(BundleContext bundleContext, Map properties) { + Display display = Display.getDefault(); + if (display != null) + for (String path : imageCache.keySet()) { + for (Image image : imageCache.get(path).values()) { + display.syncExec(() -> image.dispose()); + } + } + super.destroy(bundleContext, properties); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtView.java new file mode 100644 index 000000000..e3a268153 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtView.java @@ -0,0 +1,48 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.widgets.DataPart; +import org.argeo.cms.ux.widgets.DataView; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Composite; + +/** Base class for {@link DataView}s based on an SWT {@link Composite}. */ +public abstract class AbstractSwtView extends Composite implements DataView { + private static final long serialVersionUID = -1999179054267812170L; + + protected DataPart dataPart; + + protected final SelectionListener selectionListener; + + @SuppressWarnings("unchecked") + public AbstractSwtView(Composite parent, DataPart dataPart) { + super(parent, SWT.NONE); + setLayout(CmsSwtUtils.noSpaceGridLayout()); + + this.dataPart = dataPart; + + selectionListener = new SelectionListener() { + + private static final long serialVersionUID = 4334785560035009330L; + + @Override + public void widgetSelected(SelectionEvent e) { + if (dataPart.getOnSelected() != null) + dataPart.getOnSelected().accept((TYPE) e.item.getData()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + if (dataPart.getOnAction() != null) + dataPart.getOnAction().accept((TYPE) e.item.getData()); + } + }; + + dataPart.addView(this); + addDisposeListener((e) -> dataPart.removeView(this)); + } + + public abstract void refresh(); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ContextOverlay.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ContextOverlay.java new file mode 100644 index 000000000..f7b644377 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ContextOverlay.java @@ -0,0 +1,113 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +/** + * Manages a lightweight shell which is related to a {@link Control}, typically + * in order to reproduce a dropdown semantic, but with more flexibility. + */ +public class ContextOverlay extends ScrolledPage { + private static final long serialVersionUID = 6702077429573324009L; + +// private Shell shell; + private Control control; + + private int maxHeight = 400; + + public ContextOverlay(Control control, int style) { + super(createShell(control, style), SWT.NONE); + Shell shell = getShell(); + setLayoutData(CmsSwtUtils.fillAll()); + // TODO make autohide configurable? + //shell.addShellListener(new AutoHideShellListener()); + this.control = control; + control.addDisposeListener((e) -> { + dispose(); + shell.dispose(); + }); + } + + private static Composite createShell(Control control, int style) { + if (control == null) + throw new IllegalArgumentException("Control cannot be null"); + if (control.isDisposed()) + throw new IllegalArgumentException("Control is disposed"); + Shell shell = new Shell(control.getShell(), SWT.NO_TRIM); + shell.setLayout(CmsSwtUtils.noSpaceGridLayout()); + Composite placeholder = new Composite(shell, SWT.BORDER); + placeholder.setLayoutData(CmsSwtUtils.fillAll()); + placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout()); + return placeholder; + } + + public void show() { + Point relativeControlLocation = control.getLocation(); + Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y); + + int controlWidth = control.getBounds().width; + + Shell shell = getShell(); + + layout(true, true); + shell.pack(); + shell.layout(true, true); + int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x; + if (shell.getSize().y > maxHeight) { + shell.setSize(targetShellWidth, maxHeight); + } else { + shell.setSize(targetShellWidth, shell.getSize().y); + } + + int shellHeight = shell.getSize().y; + int controlHeight = control.getBounds().height; + Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight); + int displayHeight = shell.getDisplay().getBounds().height; + if (shellLocation.y + shellHeight > displayHeight) {// bottom of page + shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight); + } + shell.setLocation(shellLocation); + + if (getChildren().length != 0) + shell.open(); + if (!control.isDisposed()) + control.setFocus(); + } + + public void hide() { + getShell().setVisible(false); + onHide(); + } + + public boolean isShellVisible() { + if (isDisposed()) + return false; + return getShell().isVisible(); + } + + /** to be overridden */ + protected void onHide() { + // does nothing by default. + } + + private class AutoHideShellListener extends ShellAdapter { + private static final long serialVersionUID = 7743287433907938099L; + + @Override + public void shellDeactivated(ShellEvent e) { + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + // silent + } + if (!control.isDisposed() && !control.isFocusControl()) + hide(); + } + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java new file mode 100644 index 000000000..e712e2fe6 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java @@ -0,0 +1,115 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.AbstractImageManager; +import org.argeo.cms.ux.CmsUxUtils; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** A stylable and editable image. */ +public abstract class EditableImage extends StyledControl { + private static final long serialVersionUID = -5689145523114022890L; + private final static CmsLog log = CmsLog.getLog(EditableImage.class); + + private Cms2DSize preferredImageSize; + private Boolean loaded = false; + + public EditableImage(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) { + super(parent, swtStyle); + this.preferredImageSize = preferredImageSize; + } + + @Override + protected void setContainerLayoutData(Composite composite) { + // composite.setLayoutData(fillWidth()); + } + + @Override + protected void setControlLayoutData(Control control) { + // control.setLayoutData(fillWidth()); + } + + /** To be overriden. */ + protected String createImgTag() { + return noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y)); + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle()); + // lbl.setLayoutData(CmsUiUtils.fillWidth()); + CmsSwtUtils.markup(lbl); + CmsSwtUtils.style(lbl, style); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + load(lbl); + return lbl; + } + + /** To be overriden. */ + protected synchronized Boolean load(Control control) { + String imgTag; + try { + imgTag = createImgTag(); + } catch (Exception e) { + // throw new CmsException("Cannot retrieve image", e); + log.error("Cannot retrieve image", e); + imgTag = noImg(preferredImageSize); + loaded = false; + } + + if (imgTag == null) { + loaded = false; + imgTag = noImg(preferredImageSize); + } else + loaded = true; + if (control != null) { + ((Label) control).setText(imgTag); + control.setSize(preferredImageSize != null + ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight()) + : getSize()); + } else { + loaded = false; + } + getParent().layout(); + return loaded; + } + + public void setPreferredSize(Cms2DSize size) { + this.preferredImageSize = size; + if (!loaded) { + load((Label) getControl()); + } + } + + protected Text createText(Composite box, String style) { + Text text = new Text(box, getStyle()); + CmsSwtUtils.style(text, style); + return text; + } + + public Cms2DSize getPreferredImageSize() { + return preferredImageSize; + } + + public static String noImg(Cms2DSize size) { +// ResourceManager rm = RWT.getResourceManager(); +// String noImgPath=rm.getLocation(AbstractImageManager.NO_IMAGE); + // FIXME load it via package service + String noImgPath = ""; + return CmsUxUtils.img(noImgPath, size); + } + + public static String noImg() { + return noImg(AbstractImageManager.NO_IMAGE_SIZE); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableText.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableText.java new file mode 100644 index 000000000..0612e8f9b --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableText.java @@ -0,0 +1,135 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable text part displaying styled text. */ +public class EditableText extends StyledControl { + private static final long serialVersionUID = -6372283442330912755L; + + private boolean editable = true; + private boolean multiLine = true; + + private Color highlightColor; + private Composite highlight; + + private boolean useTextAsLabel = false; + + public EditableText(Composite parent, int style) { + super(parent, style); + editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); + multiLine = !(SWT.SINGLE == (style & SWT.SINGLE)); + highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); + useTextAsLabel = SWT.FLAT == (style & SWT.FLAT); + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing() && getEditable()) { + return createText(box, style, true); + } else { + if (useTextAsLabel) { + return createTextLabel(box, style); + } else { + return createLabel(box, style); + } + } + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle() | SWT.WRAP); + lbl.setLayoutData(CmsSwtUtils.fillWidth()); + if (style != null) + CmsSwtUtils.style(lbl, style); + CmsSwtUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + protected Text createTextLabel(Composite box, String style) { + Text lbl = new Text(box, getStyle() | (multiLine ? SWT.MULTI : SWT.SINGLE)); + lbl.setEditable(false); + lbl.setLayoutData(CmsSwtUtils.fillWidth()); + if (style != null) + CmsSwtUtils.style(lbl, style); + CmsSwtUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + protected Text createText(Composite box, String style, boolean editable) { + highlight = new Composite(box, SWT.NONE); + highlight.setBackground(highlightColor); + GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false); + highlightGd.widthHint = 5; + highlightGd.heightHint = 3; + highlight.setLayoutData(highlightGd); + + final Text text = new Text(box, getStyle() | (multiLine ? SWT.MULTI : SWT.SINGLE) | SWT.WRAP); + text.setEditable(editable); + GridData textLayoutData = CmsSwtUtils.fillWidth(); + // textLayoutData.heightHint = preferredHeight; + text.setLayoutData(textLayoutData); + if (style != null) + CmsSwtUtils.style(text, style); + text.setFocus(); + return text; + } + + @Override + protected void clear(boolean deep) { + if (highlight != null) + highlight.dispose(); + super.clear(deep); + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) + ((Label) child).setText(text); + else if (child instanceof Text) + ((Text) child).setText(text); + } + + public Text getAsText() { + return (Text) getControl(); + } + + public Label getAsLabel() { + return (Label) getControl(); + } + + public String getText() { + Control child = getControl(); + + if (child instanceof Label) + return ((Label) child).getText(); + else if (child instanceof Text) + return ((Text) child).getText(); + else + throw new IllegalStateException("Unsupported control " + child.getClass()); + } + + /** @deprecated Use {@link #isEditable()} instead. */ + @Deprecated + public boolean getEditable() { + return isEditable(); + } + + public boolean isEditable() { + return editable; + } + + public void setUseTextAsLabel(boolean useTextAsLabel) { + this.useTextAsLabel = useTextAsLabel; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ScrolledPage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ScrolledPage.java new file mode 100644 index 000000000..135f4c147 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ScrolledPage.java @@ -0,0 +1,74 @@ +package org.argeo.cms.swt.widgets; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * A composite that can be scrolled vertically. It wraps a + * {@link ScrolledComposite} (and is being wrapped by it), simplifying its + * configuration. + */ +public class ScrolledPage extends Composite { + private static final long serialVersionUID = 1593536965663574437L; + + private ScrolledComposite scrolledComposite; + + public ScrolledPage(Composite parent, int style) { + this(parent, style, false); + } + + public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) { + super(createScrolledComposite(parent, alwaysShowScroll), style); + scrolledComposite = (ScrolledComposite) getParent(); + scrolledComposite.setContent(this); + + scrolledComposite.setExpandVertical(true); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.addControlListener(new ScrollControlListener()); + } + + private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) { + ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL); + scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll); + return scrolledComposite; + } + + @Override + public void layout(boolean changed, boolean all) { + updateScroll(); + super.layout(changed, all); + } + + public void showControl(Control control) { + scrolledComposite.showControl(control); + } + + protected void updateScroll() { + Rectangle r = scrolledComposite.getClientArea(); + Point preferredSize = computeSize(r.width, SWT.DEFAULT); + scrolledComposite.setMinHeight(preferredSize.y); + } + + // public ScrolledComposite getScrolledComposite() { + // return this.scrolledComposite; + // } + + /** Set it on the wrapping scrolled composite */ + @Override + public void setLayoutData(Object layoutData) { + scrolledComposite.setLayoutData(layoutData); + } + + private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter { + private static final long serialVersionUID = -3586986238567483316L; + + public void controlResized(ControlEvent e) { + updateScroll(); + } + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/StyledControl.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/StyledControl.java new file mode 100644 index 000000000..82c04a26c --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/StyledControl.java @@ -0,0 +1,151 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.api.cms.ux.CmsStyle; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.SwtEditablePart; +import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** Editable text part displaying styled text. */ +public abstract class StyledControl extends Composite implements SwtEditablePart { + private static final long serialVersionUID = -6372283442330912755L; + private Control control; + + private Composite container; + private Composite box; + + protected MouseListener mouseListener; + protected FocusListener focusListener; + + private Boolean editing = Boolean.FALSE; + + private Composite ancestorToLayout; + + public StyledControl(Composite parent, int swtStyle) { + super(parent, swtStyle); + setLayout(CmsSwtUtils.noSpaceGridLayout()); + } + + protected abstract Control createControl(Composite box, String style); + + protected Composite createBox() { + Composite box = new Composite(container, SWT.INHERIT_DEFAULT); + setContainerLayoutData(box); + box.setLayout(CmsSwtUtils.noSpaceGridLayout(3)); + return box; + } + + protected Composite createContainer() { + Composite container = new Composite(this, SWT.INHERIT_DEFAULT); + setContainerLayoutData(container); + container.setLayout(CmsSwtUtils.noSpaceGridLayout()); + return container; + } + + @Override + public Control getControl() { + return control; + } + + protected synchronized Boolean isEditing() { + return editing; + } + + @Override + public synchronized void startEditing() { + assert !isEditing(); + editing = true; + // int height = control.getSize().y; + String style = (String) EclipseUiSpecificUtils.getStyleData(control); + clear(false); + refreshControl(style); + + // add the focus listener to the newly created edition control + if (focusListener != null) + control.addFocusListener(focusListener); + } + + @Override + public synchronized void stopEditing() { + assert isEditing(); + editing = false; + String style = (String) EclipseUiSpecificUtils.getStyleData(control); + clear(false); + refreshControl(style); + } + + protected void refreshControl(String style) { + control = createControl(box, style); + setControlLayoutData(control); + if (ancestorToLayout != null) + ancestorToLayout.layout(true, true); + else + getParent().layout(true, true); + } + + public void setStyle(CmsStyle style) { + setStyle(style.style()); + } + + public void setStyle(String style) { + Object currentStyle = null; + if (control != null) + currentStyle = EclipseUiSpecificUtils.getStyleData(control); + if (currentStyle != null && currentStyle.equals(style)) + return; + + clear(true); + refreshControl(style); + + if (style != null) { + CmsSwtUtils.style(box, style + "_box"); + CmsSwtUtils.style(container, style + "_container"); + } + } + + /** To be overridden */ + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsSwtUtils.fillWidth()); + } + + /** To be overridden */ + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsSwtUtils.fillWidth()); + } + + protected void clear(boolean deep) { + if (deep) { + for (Control control : getChildren()) + control.dispose(); + container = createContainer(); + box = createBox(); + } else { + control.dispose(); + } + } + + public void setMouseListener(MouseListener mouseListener) { + if (this.mouseListener != null && control != null) + control.removeMouseListener(this.mouseListener); + this.mouseListener = mouseListener; + if (control != null && this.mouseListener != null) + control.addMouseListener(mouseListener); + } + + public void setFocusListener(FocusListener focusListener) { + if (this.focusListener != null && control != null) + control.removeFocusListener(this.focusListener); + this.focusListener = focusListener; + if (control != null && this.focusListener != null) + control.addFocusListener(focusListener); + } + + public void setAncestorToLayout(Composite ancestorToLayout) { + this.ancestorToLayout = ancestorToLayout; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java new file mode 100644 index 000000000..15d531de1 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java @@ -0,0 +1,207 @@ +package org.argeo.cms.swt.widgets; + +import java.util.List; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.Selected; +import org.argeo.cms.swt.dialogs.LightweightDialog; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.argeo.cms.ux.widgets.GuidedForm; +import org.argeo.cms.ux.widgets.GuidedForm.Page; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** A wizard dialog based on {@link LightweightDialog}. */ +public class SwtGuidedFormDialog extends LightweightDialog implements GuidedForm.View { + private GuidedForm guidedForm; + private GuidedForm.Page currentPage; + private int currentPageIndex; + + private Label titleBar; + private Label message; + private Composite[] pageBodies; + private Composite buttons; + private Button back; + private Button next; + private Button finish; + + public SwtGuidedFormDialog(Shell parentShell, GuidedForm guidedForm) { + super(parentShell); + this.guidedForm = guidedForm; + guidedForm.setView(this); + // create the pages + guidedForm.addPages(); + for (Page page : guidedForm.getPages()) { + if (!(page instanceof SwtGuidedFormPage)) + throw new IllegalArgumentException("Pages form must implement " + SwtGuidedFormPage.class); + } + currentPage = guidedForm.getStartingPage(); + if (currentPage == null) + throw new IllegalArgumentException("At least one wizard page is required"); + } + + @Override + protected Control createDialogArea(Composite parent) { + updateWindowTitle(); + + Composite messageArea = new Composite(parent, SWT.NONE); + messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + { + messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); + titleBar = new Label(messageArea, SWT.WRAP); + titleBar.setFont(EclipseUiUtils.getBoldFont(parent)); + titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false)); + updateTitleBar(); + Button cancelButton = new Button(messageArea, SWT.FLAT); + cancelButton.setText(CmsMsg.cancel.lead()); + cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3)); + cancelButton.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.CANCEL)); + message = new Label(messageArea, SWT.WRAP); + message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2)); + updateMessage(); + } + + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new FormLayout()); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + pageBodies = new Composite[guidedForm.getPageCount()]; + List pages = guidedForm.getPages(); + for (int i = 0; i < pages.size(); i++) { + pageBodies[i] = new Composite(body, SWT.NONE); + pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout()); + setSwitchingFormData(pageBodies[i]); + // !! SWT specific + SwtGuidedFormPage page = (SwtGuidedFormPage) pages.get(i); + page.createControl(pageBodies[i]); + } + showPage(currentPage); + + buttons = new Composite(parent, SWT.NONE); + buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + { + boolean singlePage = guidedForm.getPageCount() == 1; + // singlePage = false;// dev + GridLayout layout = new GridLayout(singlePage ? 1 : 3, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + // TODO revert order for right-to-left languages + + if (!singlePage) { + back = new Button(buttons, SWT.PUSH); + back.setText(CmsMsg.wizardBack.lead()); + back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + back.addSelectionListener((Selected) (e) -> backPressed()); + + next = new Button(buttons, SWT.PUSH); + next.setText(CmsMsg.wizardNext.lead()); + next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + next.addSelectionListener((Selected) (e) -> nextPressed()); + } + finish = new Button(buttons, SWT.PUSH); + finish.setText(CmsMsg.wizardFinish.lead()); + finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + finish.addSelectionListener((Selected) (e) -> finishPressed()); + + updateButtons(); + } + return body; + } + + public GuidedForm.Page getCurrentPage() { + return currentPage; + } + + public Shell getShell() { + return getForegoundShell(); + } + + public void showPage(GuidedForm.Page page) { + List pages = guidedForm.getPages(); + int index = -1; + for (int i = 0; i < pages.size(); i++) { + if (page == pages.get(i)) { + index = i; + break; + } + } + if (index < 0) + throw new IllegalArgumentException("Cannot find index of wizard page " + page); + pageBodies[index].moveAbove(pageBodies[currentPageIndex]); + + // // clear + // for (Control c : body.getChildren()) + // c.dispose(); + // page.createControl(body); + // body.layout(true, true); + currentPageIndex = index; + currentPage = page; + } + + @Override + public void updateButtons() { + if (back != null) + back.setEnabled(guidedForm.getPreviousPage(currentPage) != null); + if (next != null) + next.setEnabled(guidedForm.getNextPage(currentPage) != null && currentPage.canFlipToNextPage()); + if (finish != null) { + finish.setEnabled(guidedForm.canFinish()); + } + } + + public void updateMessage() { + if (currentPage.getMessage() != null) + message.setText(currentPage.getMessage()); + } + + public void updateTitleBar() { + if (currentPage.getTitle() != null) + titleBar.setText(currentPage.getTitle()); + } + + public void updateWindowTitle() { + setTitle(guidedForm.getFormTitle()); + } + + protected boolean onCancel() { + return guidedForm.performCancel(); + } + + protected void nextPressed() { + GuidedForm.Page page = guidedForm.getNextPage(currentPage); + showPage(page); + updateButtons(); + } + + protected void backPressed() { + GuidedForm.Page page = guidedForm.getPreviousPage(currentPage); + showPage(page); + updateButtons(); + } + + protected void finishPressed() { + if (guidedForm.performFinish()) + closeShell(CmsDialog.OK); + } + + private static void setSwitchingFormData(Composite composite) { + FormData fdLabel = new FormData(); + fdLabel.top = new FormAttachment(0, 0); + fdLabel.left = new FormAttachment(0, 0); + fdLabel.right = new FormAttachment(100, 0); + fdLabel.bottom = new FormAttachment(100, 0); + composite.setLayoutData(fdLabel); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java new file mode 100644 index 000000000..f082796c2 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java @@ -0,0 +1,13 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.cms.ux.widgets.AbstractGuidedFormPage; +import org.eclipse.swt.widgets.Composite; + +public abstract class SwtGuidedFormPage extends AbstractGuidedFormPage { + + public SwtGuidedFormPage(String pageName) { + super(pageName); + } + + public abstract void createControl(Composite parent); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTableView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTableView.java new file mode 100644 index 000000000..3291980f6 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTableView.java @@ -0,0 +1,87 @@ +package org.argeo.cms.swt.widgets; + +import org.argeo.api.cms.ux.CmsIcon; +import org.argeo.cms.swt.CmsSwtTheme; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.widgets.Column; +import org.argeo.cms.ux.widgets.TabularPart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +/** View of a {@link TabularPart} based on a {@link Table}. */ +public class SwtTableView extends AbstractSwtView { + private static final long serialVersionUID = -1114155772446357750L; + private final Table table; + private TabularPart tabularPart; + + private CmsSwtTheme theme; + + public SwtTableView(Composite parent, int style, TabularPart tabularPart) { + super(parent, tabularPart); + theme = CmsSwtUtils.getCmsTheme(parent); + + table = new Table(this, SWT.VIRTUAL | style); + table.setLinesVisible(true); + table.setLayoutData(CmsSwtUtils.fillAll()); + + this.tabularPart = tabularPart; + } + + @Override + public void refresh() { + // TODO optimise + table.clearAll(); + table.addListener(SWT.SetData, event -> { + TableItem item = (TableItem) event.item; + refreshItem(item); + }); + table.setItemCount(tabularPart.getItemCount()); + for (int i = 0; i < tabularPart.getColumnCount(); i++) { + TableColumn swtColumn = new TableColumn(table, SWT.NONE); + swtColumn.setWidth(tabularPart.getColumn(i).getWidth()); + } + CmsSwtUtils.fill(table); + + table.addSelectionListener(selectionListener); + + } + + protected Object getDataFromEvent(SelectionEvent e) { + Object data = e.item.getData(); + if (data == null) + data = tabularPart.getData(getTable().indexOf((TableItem) e.item)); + return data; + } + + protected void refreshItem(TableItem item) { + int row = getTable().indexOf(item); + T data = tabularPart.getData(row); + for (int i = 0; i < tabularPart.getColumnCount(); i++) { + Column column = tabularPart.getColumn(i); + item.setData(data); + String text = data != null ? column.getText(data) : ""; + if (text != null) + item.setText(i, text); + CmsIcon icon = column.getIcon(data); + if (icon != null) { + Image image = theme.getSmallIcon(icon); + item.setImage(i, image); + } + } + } + + @Override + public void notifyItemCountChange() { + table.setItemCount(tabularPart.getItemCount()); + } + + protected Table getTable() { + return table; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTreeView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTreeView.java new file mode 100644 index 000000000..778ed4138 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTreeView.java @@ -0,0 +1,108 @@ +package org.argeo.cms.swt.widgets; + +import java.util.List; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsIcon; +import org.argeo.cms.swt.CmsSwtTheme; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ux.widgets.Column; +import org.argeo.cms.ux.widgets.HierarchicalPart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +/** View of a {@link HierarchicalPart} based on a {@link Tree}. */ +public class SwtTreeView extends AbstractSwtView { + private final static CmsLog log = CmsLog.getLog(SwtTreeView.class); + + private static final long serialVersionUID = -6247710601465713047L; + + private final Tree tree; + + private HierarchicalPart hierarchicalPart; + private CmsSwtTheme theme; + + public SwtTreeView(Composite parent, int style, HierarchicalPart hierarchicalPart) { + super(parent, hierarchicalPart); + theme = CmsSwtUtils.getCmsTheme(parent); + + tree = new Tree(this, style); + tree.setLayoutData(CmsSwtUtils.fillAll()); + this.hierarchicalPart = hierarchicalPart; + + tree.addSelectionListener(selectionListener); + } + + @SuppressWarnings("unchecked") + @Override + public void refresh() { + // TODO optimise + for (TreeItem rootItem : tree.getItems()) { + rootItem.dispose(); + } + + List rootItems = hierarchicalPart.getChildren(hierarchicalPart.getInput()); + for (T child : rootItems) { + try { + addTreeItem(null, child); + } catch (Exception e) { + if (log.isTraceEnabled()) + log.error("Cannot retrieve child", e); + } + } + + tree.addListener(SWT.Expand, event -> { + final TreeItem root = (TreeItem) event.item; + TreeItem[] items = root.getItems(); + for (TreeItem item : items) { + if (item.getData() != null) { + return; + } + item.dispose(); + } + + List children = hierarchicalPart.getChildren((T) root.getData()); + for (T child : children) { + addTreeItem(root, child); + } + }); + + CmsSwtUtils.fill(tree); + + } + + protected TreeItem addTreeItem(TreeItem parent, T data) { + TreeItem item = parent == null ? new TreeItem(tree, SWT.NONE) : new TreeItem(parent, SWT.NONE); + item.setData(data); + for (int i = 0; i < hierarchicalPart.getColumnCount(); i++) { + Column column = hierarchicalPart.getColumn(i); + String txt = column.getText(data); + if (txt != null) + item.setText(txt); + CmsIcon icon = column.getIcon(data); + if (icon != null) { + Image image = theme.getSmallIcon(icon); + item.setImage(image); + } + } + // TODO optimise + List grandChildren = hierarchicalPart.getChildren(data); + if (grandChildren.size() != 0) + new TreeItem(item, SWT.NONE); + return item; + } + + @Override + public void notifyItemCountChange() { + // TODO what to update ? + + } + + protected Tree getTree() { + return tree; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java new file mode 100644 index 000000000..64ea2dbc9 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java @@ -0,0 +1,43 @@ +package org.argeo.eclipse.ui; + +import org.argeo.cms.ux.widgets.TreeParent; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Tree content provider dealing with tree objects and providing reasonable + * defaults. + */ +public abstract class AbstractTreeContentProvider implements + ITreeContentProvider { + private static final long serialVersionUID = 8246126401957763868L; + + /** Does nothing */ + public void dispose() { + } + + /** Does nothing */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + public Object[] getChildren(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).getChildren(); + } + return new Object[0]; + } + + public Object getParent(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).getParent(); + } + return null; + } + + public boolean hasChildren(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).hasChildren(); + } + return false; + } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java new file mode 100644 index 000000000..a38552c07 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java @@ -0,0 +1,68 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.ColumnLabelProvider; + +/** + * Wraps the definition of a column to be used in the various JFace viewers + * (typically tree and table). It enables definition of generic viewers which + * column can be then defined externally. Also used to generate export. + */ +public class ColumnDefinition { + private ColumnLabelProvider labelProvider; + private String label; + private int weight = 0; + private int minWidth = 120; + + public ColumnDefinition(ColumnLabelProvider labelProvider, String label) { + this.labelProvider = labelProvider; + this.label = label; + } + + public ColumnDefinition(ColumnLabelProvider labelProvider, String label, + int weight) { + this.labelProvider = labelProvider; + this.label = label; + this.weight = weight; + this.minWidth = weight; + } + + public ColumnDefinition(ColumnLabelProvider labelProvider, String label, + int weight, int minimumWidth) { + this.labelProvider = labelProvider; + this.label = label; + this.weight = weight; + this.minWidth = minimumWidth; + } + + public ColumnLabelProvider getLabelProvider() { + return labelProvider; + } + + public void setLabelProvider(ColumnLabelProvider labelProvider) { + this.labelProvider = labelProvider; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public int getMinWidth() { + return minWidth; + } + + public void setMinWidth(int minWidth) { + this.minWidth = minWidth; + } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java new file mode 100644 index 000000000..9430a2083 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java @@ -0,0 +1,81 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; + +/** Generic column viewer sorter */ +public class ColumnViewerComparator extends ViewerComparator { + private static final long serialVersionUID = -2266218906355859909L; + + public static final int ASC = 1; + + public static final int NONE = 0; + + public static final int DESC = -1; + + private int direction = 0; + + private TableViewerColumn column; + + private ColumnViewer viewer; + + public ColumnViewerComparator(TableViewerColumn column) { + super(null); + this.column = column; + this.viewer = column.getViewer(); + this.column.getColumn().addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 7586796298965472189L; + + public void widgetSelected(SelectionEvent e) { + if (ColumnViewerComparator.this.viewer.getComparator() != null) { + if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) { + int tdirection = ColumnViewerComparator.this.direction; + + if (tdirection == ASC) { + setSortDirection(DESC); + } else if (tdirection == DESC) { + setSortDirection(NONE); + } + } else { + setSortDirection(ASC); + } + } else { + setSortDirection(ASC); + } + } + }); + } + + private void setSortDirection(int direction) { + if (direction == NONE) { + column.getColumn().getParent().setSortColumn(null); + column.getColumn().getParent().setSortDirection(SWT.NONE); + viewer.setComparator(null); + } else { + column.getColumn().getParent().setSortColumn(column.getColumn()); + this.direction = direction; + + if (direction == ASC) { + column.getColumn().getParent().setSortDirection(SWT.DOWN); + } else { + column.getColumn().getParent().setSortDirection(SWT.UP); + } + + if (viewer.getComparator() == this) { + viewer.refresh(); + } else { + viewer.setComparator(this); + } + + } + } + + public int compare(Viewer viewer, Object e1, Object e2) { + return direction * super.compare(viewer, e1, e2); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java new file mode 100644 index 000000000..37a36e859 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java @@ -0,0 +1,15 @@ +package org.argeo.eclipse.ui; + +/** CMS specific exceptions. */ +public class EclipseUiException extends RuntimeException { + private static final long serialVersionUID = -5341764743356771313L; + + public EclipseUiException(String message) { + super(message); + } + + public EclipseUiException(String message, Throwable e) { + super(message, e); + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java new file mode 100644 index 000000000..95b45fed6 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java @@ -0,0 +1,195 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Utilities to simplify UI development. */ +public class EclipseUiUtils { + + /** Dispose all children of a Composite */ + public static void clear(Composite composite) { + for (Control child : composite.getChildren()) + child.dispose(); + } + + /** + * Enables efficient call to the layout method of a composite, refreshing only + * some of the children controls. + */ + public static void layout(Composite parent, Control... toUpdateControls) { + parent.layout(toUpdateControls); + } + + // + // FONTS + // + /** Shortcut to retrieve default italic font from display */ + public static Font getItalicFont(Composite parent) { + return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC) + .createFont(parent.getDisplay()); + } + + /** Shortcut to retrieve default bold font from display */ + public static Font getBoldFont(Composite parent) { + return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) + .createFont(parent.getDisplay()); + } + + /** Shortcut to retrieve default bold italic font from display */ + public static Font getBoldItalicFont(Composite parent) { + return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC) + .createFont(parent.getDisplay()); + } + + // + // Simplify grid layouts management + // + public static GridLayout noSpaceGridLayout() { + return noSpaceGridLayout(new GridLayout()); + } + + public static GridLayout noSpaceGridLayout(int columns) { + return noSpaceGridLayout(new GridLayout(columns, false)); + } + + public static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static GridData fillWidth() { + return grabWidth(SWT.FILL, SWT.FILL); + } + + public static GridData fillWidth(int colSpan) { + GridData gd = grabWidth(SWT.FILL, SWT.FILL); + gd.horizontalSpan = colSpan; + return gd; + } + + public static GridData fillAll() { + return new GridData(SWT.FILL, SWT.FILL, true, true); + } + + public static GridData fillAll(int colSpan, int rowSpan) { + return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan); + } + + public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, true, false); + } + + // + // Simplify Form layout management + // + + /** + * Creates a basic form data that is attached to the 4 corners of the parent + * composite + */ + public static FormData fillFormData() { + FormData formData = new FormData(); + formData.top = new FormAttachment(0, 0); + formData.left = new FormAttachment(0, 0); + formData.right = new FormAttachment(100, 0); + formData.bottom = new FormAttachment(100, 0); + return formData; + } + + /** + * Create a label and a text field for a grid layout, the text field grabbing + * excess horizontal + * + * @param parent + * the parent composite + * @param label + * the label to display + * @param modifyListener + * a {@link ModifyListener} to listen on events on the text, can be + * null + * @return the created text + * + */ + // FIXME why was this deprecated. + // * @ deprecated use { @ link #createGridLT(Composite, String)} instead + // @ Deprecated + public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Text txt = new Text(parent, SWT.LEAD | SWT.BORDER); + txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + if (modifyListener != null) + txt.addModifyListener(modifyListener); + return txt; + } + + /** + * Create a label and a text field for a grid layout, the text field grabbing + * excess horizontal + */ + public static Text createGridLT(Composite parent, String label) { + return createGridLT(parent, label, null); + } + + /** + * Creates one label and a text field not editable with background colour of the + * parent (like a label but with selectable text) + */ + public static Text createGridLL(Composite parent, String label, String text) { + Text txt = createGridLT(parent, label); + txt.setText(text); + txt.setEditable(false); + txt.setBackground(parent.getBackground()); + return txt; + } + + /** + * Create a label and a text field with password display for a grid layout, the + * text field grabbing excess horizontal + */ + public static Text createGridLP(Composite parent, String label) { + return createGridLP(parent, label, null); + } + + /** + * Create a label and a text field with password display for a grid layout, the + * text field grabbing excess horizontal. The given modify listener will be + * added to the newly created text field if not null. + */ + public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD); + txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + if (modifyListener != null) + txt.addModifyListener(modifyListener); + return txt; + } + + // MISCELLANEOUS + + /** Simply checks if a string is not null nor empty */ + public static boolean notEmpty(String stringToTest) { + return !(stringToTest == null || "".equals(stringToTest.trim())); + } + + /** Simply checks if a string is null or empty */ + public static boolean isEmpty(String stringToTest) { + return stringToTest == null || "".equals(stringToTest.trim()); + } +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java new file mode 100644 index 000000000..e82505df5 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java @@ -0,0 +1,16 @@ +package org.argeo.eclipse.ui; + +import java.io.InputStream; + +/** + * Used for file download : subclasses must implement model specific methods to + * get a byte array representing a file given is ID. + */ +@Deprecated +public interface FileProvider { + + public byte[] getByteArrayFileFromId(String fileId); + + public InputStream getInputStreamFromFileId(String fileId); + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java new file mode 100644 index 000000000..e1d8b05ea --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java @@ -0,0 +1,39 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; + +public abstract class GenericTableComparator extends ViewerComparator { + private static final long serialVersionUID = -1175894935075325810L; + protected int propertyIndex; + public static final int ASCENDING = 0, DESCENDING = 1; + protected int direction = DESCENDING; + + /** + * Creates an instance of a sorter for TableViewer. + * + * @param defaultColumnIndex + * the default sorter column + */ + + public GenericTableComparator(int defaultColumnIndex, int direction) { + propertyIndex = defaultColumnIndex; + this.direction = direction; + } + + public void setColumn(int column) { + if (column == this.propertyIndex) { + // Same column as last sort; toggle the direction + direction = 1 - direction; + } else { + // New column; do a descending sort + this.propertyIndex = column; + direction = DESCENDING; + } + } + + /** + * Must be Overriden in each view. + */ + public abstract int compare(Viewer viewer, Object e1, Object e2); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java new file mode 100644 index 000000000..ac7b2d8fb --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java @@ -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 getColumnDefinition(String extractId); +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java new file mode 100644 index 000000000..c01b2d751 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java @@ -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 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 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 = "Unknown"; + addProperty(parent, "Type", mimeType); + addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false)); + } + parent.layout(true, true); + } catch (IOException e) { + throw new FsUiException("Cannot display details for " + context, e); + } + } + + private void addProperty(Composite parent, String propName, String value) { + Label contextL = new Label(parent, SWT.NONE); + contextL.setText(propName + ": " + value); + } + + /** + * Almost canonical implementation of a table that displays the content of a + * directory + */ + private class FilterEntitiesVirtualTable extends Composite { + private static final long serialVersionUID = 2223410043691844875L; + + // Context + private Path context; + private Path currSelected = null; + + // UI Objects + private FsTableViewer viewer; + + @Override + public boolean setFocus() { + if (viewer.getTable().isDisposed()) + return false; + if (currSelected != null) + viewer.setSelection(new StructuredSelection(currSelected), true); + else if (viewer.getSelection().isEmpty()) { + Object first = viewer.getElementAt(0); + if (first != null) + viewer.setSelection(new StructuredSelection(first), true); + } + return viewer.getTable().setFocus(); + } + + /** + * Enable highlighting the correct element in the table when externally browsing + * (typically via the command-line-like Text field) + */ + void setSelected(Path selected) { + // to prevent change selection event to be thrown + currSelected = selected; + viewer.setSelection(new StructuredSelection(currSelected), true); + } + + void filterList(String filter) { + viewer.setInput(context, filter); + } + + public FilterEntitiesVirtualTable(Composite parent, int style, Path context) { + super(parent, SWT.NO_FOCUS); + this.context = context; + createTableViewer(this); + } + + private void createTableViewer(final Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // We must limit the size of the table otherwise the full list is + // loaded before the layout happens + // Composite listCmp = new Composite(parent, SWT.NO_FOCUS); + // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); + // gd.widthHint = COLUMN_WIDTH; + // listCmp.setLayoutData(gd); + // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI | + // SWT.V_SCROLL); + // Table table = viewer.getTable(); + // table.setLayoutData(EclipseUiUtils.fillAll()); + + viewer = new FsTableViewer(parent, SWT.MULTI); + Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH); + + viewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); + if (selection.isEmpty()) + return; + Object obj = selection.getFirstElement(); + Path newSelected; + if (obj instanceof Path) + newSelected = (Path) obj; + else if (obj instanceof ParentDir) + newSelected = ((ParentDir) obj).getPath(); + else + return; + if (newSelected.equals(currSelected)) + return; + currSelected = newSelected; + setEdited(newSelected); + + } + }); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8083424284436715709L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (e.keyCode == SWT.ARROW_RIGHT) { + if (!Files.isDirectory(selected)) + return; + if (selected != null) { + setEdited(selected); + browserCols.get(selected).setFocus(); + } + } else if (e.keyCode == SWT.ARROW_LEFT) { + if (context.equals(initialPath)) + return; + Path parent = context.getParent(); + if (parent == null) + return; + + setEdited(parent); + browserCols.get(parent).setFocus(); + } + } + }); + } + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java new file mode 100644 index 000000000..d3fc1c903 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java @@ -0,0 +1,84 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Image; + +/** Basic label provider with icon for NIO file viewers */ +public class FileIconNameLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = 8187902187946523148L; + + private Image folderIcon; + private Image fileIcon; + + public FileIconNameLabelProvider() { + // if (!PlatformUI.isWorkbenchRunning()) { + folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage(); + fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage(); + // } + } + + @Override + public void dispose() { + if (folderIcon != null) + folderIcon.dispose(); + if (fileIcon != null) + fileIcon.dispose(); + super.dispose(); + } + + @Override + public String getText(Object element) { + if (element instanceof Path) { + Path curr = ((Path) element); + Path name = curr.getFileName(); + if (name == null) + return "[No name]"; + else + return name.toString(); + } else if (element instanceof ParentDir) { + return ".."; + } + return null; + } + + @Override + public Image getImage(Object element) { + if (element instanceof Path) { + Path curr = ((Path) element); + if (Files.isDirectory(curr)) + // if (folderIcon != null) + return folderIcon; + // else + // return + // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); + // else if (fileIcon != null) + return fileIcon; + // else + // return + // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); + } else if (element instanceof ParentDir) { + return folderIcon; + } + return null; + } + + @Override + public String getToolTipText(Object element) { + if (element instanceof Path) { + Path curr = ((Path) element); + Path name = curr.getFileName(); + if (name == null) + return "[No name]"; + else + return name.toAbsolutePath().toString(); + } else if (element instanceof ParentDir) { + return ((ParentDir) element).getPath().toAbsolutePath().toString(); + } + return null; + } + +} \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java new file mode 100644 index 000000000..3b126e90b --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java @@ -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 columns) { + this.setContentProvider(new MyLazyCP()); + Table table = this.getTable(); + table.setLinesVisible(true); + table.setHeaderVisible(true); + // CmsUtils.markup(table); + // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); + for (ColumnDefinition colDef : columns) { + TableViewerColumn column = new TableViewerColumn(this, SWT.NONE); + column.setLabelProvider(colDef.getLabelProvider()); + TableColumn tcol = column.getColumn(); + tcol.setResizable(true); + tcol.setText(colDef.getLabel()); + tcol.setWidth(colDef.getMinWidth()); + } + return table; + } + + public void setInput(Path dir, String filter) { + Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder); + if (rows == null) { + this.setInput(null); + this.setItemCount(0); + return; + } + boolean isRoot; + try { + isRoot = dir.getRoot().equals(dir); + } catch (Exception e) { + // FIXME Workaround for JCR root node access + isRoot = dir.toString().equals("/"); + } + final Object[] res; + if (isRoot) + res = rows; + else if (initialPath != null && initialPath.equals(dir)) + res = rows; + else { + res = new Object[rows.length + 1]; + res[0] = new ParentDir(dir.getParent()); + for (int i = 1; i < res.length; i++) { + res[i] = rows[i - 1]; + } + } + this.setInput(res); + int length = res.length; + this.setItemCount(length); + this.refresh(); + } + + /** Directly displays bookmarks **/ + public void setPathsInput(Path... paths) { + this.setInput((Object[]) paths); + this.setItemCount(paths.length); + this.refresh(); + } + + /** + * A path which is to be considered as root (and thus provide no link to a + * parent directory) + */ + public void setInitialPath(Path initialPath) { + this.initialPath = initialPath; + } + + private class MyLazyCP implements ILazyContentProvider { + private static final long serialVersionUID = 9096550041395433128L; + private Object[] elements; + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // IMPORTANT: don't forget this: an exception will be thrown if + // a selected object is not part of the results anymore. + viewer.setSelection(null); + this.elements = (Object[]) newInput; + } + + public void updateElement(int index) { + if (index < elements.length) + FsTableViewer.this.replace(elements[index], index); + } + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java new file mode 100644 index 000000000..f55ead718 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java @@ -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 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 stream = Files.newDirectoryStream(dir, filter)) { + // TODO make this lazy + List 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 children = Files.newDirectoryStream(path, "*")) { + return children.iterator().hasNext(); + } + } catch (IOException e) { + throw new FsUiException("Unable to check child existence on " + path, e); + } + } + + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java new file mode 100644 index 000000000..2b51e71a2 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java @@ -0,0 +1,11 @@ +package org.argeo.eclipse.ui.fs; + +/** Centralize constants used by the Nio FS UI parts */ +public interface FsUiConstants { + + // TODO use standard properties + String PROPERTY_NAME = "name"; + String PROPERTY_SIZE = "size"; + String PROPERTY_LAST_MODIFIED = "last-modified"; + String PROPERTY_TYPE = "type"; +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java new file mode 100644 index 000000000..422b0e1ad --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java @@ -0,0 +1,14 @@ +package org.argeo.eclipse.ui.fs; + +/** Files specific exception */ +public class FsUiException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public FsUiException(String message) { + super(message); + } + + public FsUiException(String message, Throwable e) { + super(message, e); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java new file mode 100644 index 000000000..956d96bb5 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java @@ -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 pairs = new ArrayList<>(); + try (DirectoryStream 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 { + Path p; + Boolean i; + }; + + static class LPair extends Pair { + long v; + + public LPair(Path path, long propValue) { + p = path; + v = propValue; + } + + public LPair(Path path, long propValue, boolean isDir) { + p = path; + v = propValue; + i = isDir; + } + + public int compareTo(Object o) { + if (i != null) { + Boolean j = ((LPair) o).i; + if (i.booleanValue() != j.booleanValue()) + return i.booleanValue() ? -1 : 1; + } + long u = ((LPair) o).v; + return v < u ? -1 : v == u ? 0 : 1; + } + }; + + static class SPair extends Pair { + String v; + + public SPair(Path path, String propValue) { + p = path; + v = propValue; + } + + public SPair(Path path, String propValue, boolean isDir) { + p = path; + v = propValue; + i = isDir; + } + + public int compareTo(Object o) { + if (i != null) { + Boolean j = ((SPair) o).i; + if (i.booleanValue() != j.booleanValue()) + return i.booleanValue() ? -1 : 1; + } + String u = ((SPair) o).v; + return v.compareTo(u); + } + }; +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java new file mode 100644 index 000000000..2bb65eed0 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java @@ -0,0 +1,78 @@ +package org.argeo.eclipse.ui.fs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; + +/** Expect a {@link Path} as input element */ +public class NioFileLabelProvider extends ColumnLabelProvider { + private final static FileTime EPOCH = FileTime.fromMillis(0); + private static final long serialVersionUID = 2160026425187796930L; + private final String propName; + private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm"); + + // TODO use new formatting + // DateTimeFormatter formatter = + // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT ) + // .withLocale( Locale.UK ) + // .withZone( ZoneId.systemDefault() ); + public NioFileLabelProvider(String propName) { + this.propName = propName; + } + + @Override + public String getText(Object element) { + try { + Path path; + if (element instanceof ParentDir) { +// switch (propName) { +// case FsUiConstants.PROPERTY_SIZE: +// return "-"; +// case FsUiConstants.PROPERTY_LAST_MODIFIED: +// return "-"; +// // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString(); +// case FsUiConstants.PROPERTY_TYPE: +// return "Folder"; +// } + path = ((ParentDir) element).getPath(); + } else + path = (Path) element; + switch (propName) { + case FsUiConstants.PROPERTY_SIZE: + if (Files.isDirectory(path)) + return "-"; + else + return FsUiUtils.humanReadableByteCount(Files.size(path), false); + case FsUiConstants.PROPERTY_LAST_MODIFIED: + if (Files.isDirectory(path)) + return "-"; + FileTime time = Files.getLastModifiedTime(path); + if (time.equals(EPOCH)) + return "-"; + else + return dateFormat.format(new Date(time.toMillis())); + case FsUiConstants.PROPERTY_TYPE: + if (Files.isDirectory(path)) + return "Folder"; + else { + String mimeType = Files.probeContentType(path); + if (EclipseUiUtils.isEmpty(mimeType)) + return "Unknown"; + else + return mimeType; + } + default: + throw new IllegalArgumentException("Unsupported property " + propName); + } + } catch (IOException ioe) { + throw new FsUiException("Cannot get property " + propName + " on " + element); + } + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java new file mode 100644 index 000000000..6f09c2905 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java @@ -0,0 +1,28 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Path; + +/** A parent directory (..) reference. */ +public class ParentDir { + Path path; + + public ParentDir(Path path) { + super(); + this.path = path; + } + + public Path getPath() { + return path; + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public String toString() { + return "Parent dir " + path; + } + +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java new file mode 100644 index 000000000..2e3d6b405 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java @@ -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 colDefs = new ArrayList<>(); + colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), + "Last modified", 200)); + Table table = directoryDisplayViewer.configureDefaultTable(colDefs); + table.setLayoutData(EclipseUiUtils.fillAll()); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8083424284436715709L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + log.debug("Key event received: " + e.keyCode); + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (e.keyCode == SWT.CR) { + if (!Files.isDirectory(selected)) + return; + if (selected != null) { + currSelected = selected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } else if (e.keyCode == SWT.BS) { + currSelected = currSelected.getParent(); + directoryDisplayViewer.setInput(currSelected, "*"); + directoryDisplayViewer.getTable().setFocus(); + } + } + }); + +// directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { +// @Override +// public void doubleClick(DoubleClickEvent event) { +// IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); +// Path selected = null; +// if (!selection.isEmpty()) { +// Object obj = selection.getFirstElement(); +// if (obj instanceof Path) +// selected = (Path) obj; +// else if (obj instanceof ParentDir) +// selected = ((ParentDir) obj).getPath(); +// } +// if (selected != null) { +// if (!Files.isDirectory(selected)) +// return; +// currSelected = selected; +// directoryDisplayViewer.setInput(currSelected, "*"); +// } +// } +// }); + + directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) { + Object obj = selection.getFirstElement(); + if (obj instanceof Path) + selected = (Path) obj; + else if (obj instanceof ParentDir) + selected = ((ParentDir) obj).getPath(); + } + if (selected != null) { + if (!Files.isDirectory(selected)) + return; + currSelected = selected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } + }); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java new file mode 100644 index 000000000..401e5cb5e --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java @@ -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 colDefs = new ArrayList<>(); + colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), + "Last modified", 100, 100)); + Table table = directoryDisplayViewer.configureDefaultTable(colDefs); + table.setLayoutData(EclipseUiUtils.fillAll()); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8083424284436715709L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + log.debug("Key event received: " + e.keyCode); + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (e.keyCode == SWT.CR) { + if (!Files.isDirectory(selected)) + return; + if (selected != null) { + currSelected = selected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } else if (e.keyCode == SWT.BS) { + currSelected = currSelected.getParent(); + directoryDisplayViewer.setInput(currSelected, "*"); + directoryDisplayViewer.getTable().setFocus(); + } + } + }); + } +} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png new file mode 100644 index 000000000..ce2f2a8f4 Binary files /dev/null and b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png differ diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png new file mode 100644 index 000000000..c31f37e07 Binary files /dev/null and b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png differ diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java new file mode 100644 index 000000000..d7f83c3e1 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace file system utilities. */ +package org.argeo.eclipse.ui.fs; \ No newline at end of file diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java new file mode 100644 index 000000000..0d245db07 --- /dev/null +++ b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace utilities. */ +package org.argeo.eclipse.ui; \ No newline at end of file diff --git a/swt/org.argeo.swt.minidesktop/.classpath b/swt/org.argeo.swt.minidesktop/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/org.argeo.swt.minidesktop/.gitignore b/swt/org.argeo.swt.minidesktop/.gitignore new file mode 100644 index 000000000..97adb723b --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/target/ +*.log \ No newline at end of file diff --git a/swt/org.argeo.swt.minidesktop/.project b/swt/org.argeo.swt.minidesktop/.project new file mode 100644 index 000000000..b6c2c1ae4 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/.project @@ -0,0 +1,28 @@ + + + org.argeo.swt.minidesktop + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/org.argeo.swt.minidesktop/META-INF/.gitignore b/swt/org.argeo.swt.minidesktop/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/org.argeo.swt.minidesktop/bnd.bnd b/swt/org.argeo.swt.minidesktop/bnd.bnd new file mode 100644 index 000000000..f3c13bec5 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/bnd.bnd @@ -0,0 +1,2 @@ +Import-Package: org.eclipse.swt,\ +* diff --git a/swt/org.argeo.swt.minidesktop/build.properties b/swt/org.argeo.swt.minidesktop/build.properties new file mode 100644 index 000000000..34d2e4d2d --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java new file mode 100644 index 000000000..e7bb9e856 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java @@ -0,0 +1,186 @@ +package org.argeo.minidesktop; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.browser.LocationAdapter; +import org.eclipse.swt.browser.LocationEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** A very minimalistic web browser based on {@link Browser}. */ +public class MiniBrowser { + private static Point defaultShellSize = new Point(800, 480); + + private Browser browser; + private Text addressT; + + private final boolean fullscreen; + private final boolean appMode; + + public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) { + this.fullscreen = fullscreen; + this.appMode = appMode; + createUi(composite); + setUrl(url); + } + + public Control createUi(Composite parent) { + parent.setLayout(noSpaceGridLayout(new GridLayout())); + if (!isAppMode()) { + Control toolBar = createToolBar(parent); + toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + Control body = createBody(parent); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return body; + } + + protected Control createToolBar(Composite parent) { + Composite toolBar = new Composite(parent, SWT.NONE); + toolBar.setLayout(new FillLayout()); + addressT = new Text(toolBar, SWT.SINGLE); + addressT.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + setUrl(addressT.getText().trim()); + } + }); + return toolBar; + } + + protected Control createBody(Composite parent) { + browser = new Browser(parent, SWT.NONE); + if (isFullScreen()) + browser.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W + browser.getShell().dispose(); + } + } + }); + browser.addLocationListener(new LocationAdapter() { + @Override + public void changed(LocationEvent event) { + System.out.println(event); + if (addressT != null) + addressT.setText(event.location); + } + + }); + + MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserTitleListener(this, browser); + MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserOpenWindowListener(this, browser); + return browser; + } + + public Browser openNewBrowserWindow() { + + if (isFullScreen()) { + // TODO manage multiple tabs? + return browser; + } else { + Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM); + MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode()); + newShell.setSize(defaultShellSize); + newShell.open(); + return newMiniBrowser.browser; + } + } + + protected boolean isFullScreen() { + return fullscreen; + } + + void setUrl(String url) { + if (browser != null && url != null && !url.equals(browser.getUrl())) + browser.setUrl(url.toString()); + } + + /** Called when URL changed; to be overridden, does nothing by default. */ + protected void urlChanged(String url) { + } + + /** Called when title changed; to be overridden, does nothing by default. */ + public void titleChanged(String title) { + } + + protected Browser getBrowser() { + return browser; + } + + protected boolean isAppMode() { + return appMode; + } + + private static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static void main(String[] args) { + List options = Arrays.asList(args); + if (options.contains("--help")) { + System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]"); + System.out.println("A minimalistic web browser Eclipse SWT Browser integration."); + System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)"); + System.out.println(" --app : open without an address bar and a toolbar"); + System.out.println(" --help : print this help and exit"); + System.exit(1); + } + boolean fullscreen = options.contains("--fullscreen"); + boolean appMode = options.contains("--app"); + String url = "https://start.duckduckgo.com/"; + if (options.size() > 0) { + String last = options.get(options.size() - 1); + if (!last.startsWith("--")) + url = last.trim(); + } + + Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); + Shell shell; + if (fullscreen) { + shell = new Shell(display, SWT.NO_TRIM); + shell.setFullScreen(true); + Rectangle bounds = display.getBounds(); + shell.setSize(bounds.width, bounds.height); + } else { + shell = new Shell(display, SWT.SHELL_TRIM); + shell.setSize(defaultShellSize); + } + + new MiniBrowser(shell, url, fullscreen, appMode) { + + @Override + public void titleChanged(String title) { + shell.setText(title); + } + }; + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java new file mode 100644 index 000000000..db5e6f273 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java @@ -0,0 +1,53 @@ +package org.argeo.minidesktop; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.swt.SWTException; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; + +/** Icons. */ +public class MiniDesktopImages { + + public final Image homeIcon; + public final Image exitIcon; + + public final Image terminalIcon; + public final Image browserIcon; + public final Image explorerIcon; + public final Image textEditorIcon; + + public final Image folderIcon; + public final Image fileIcon; + + public MiniDesktopImages(Display display) { + homeIcon = loadImage(display, "nav_home@2x.png"); + exitIcon = loadImage(display, "delete@2x.png"); + + terminalIcon = loadImage(display, "console_view@2x.png"); + browserIcon = loadImage(display, "external_browser@2x.png"); + explorerIcon = loadImage(display, "fldr_obj@2x.png"); + textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png"); + + folderIcon = loadImage(display, "fldr_obj@2x.png"); + fileIcon = loadImage(display, "file_obj@2x.png"); + } + + static Image loadImage(Display display, String path) { + InputStream stream = MiniDesktopImages.class.getResourceAsStream(path); + if (stream == null) + throw new IllegalArgumentException("Image " + path + " not found"); + Image image = null; + try { + image = new Image(display, stream); + } catch (SWTException ex) { + } finally { + try { + stream.close(); + } catch (IOException ex) { + } + } + return image; + } +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java new file mode 100644 index 000000000..adb2a55ba --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java @@ -0,0 +1,347 @@ +package org.argeo.minidesktop; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +/** A very minimalistic desktop manager based on Java and Eclipse SWT. */ +public class MiniDesktopManager { + private Display display; + + private Shell rootShell; + private Shell toolBarShell; + private CTabFolder tabFolder; + private int maxTabTitleLength = 16; + + private final boolean fullscreen; + private final boolean stacking; + + private MiniDesktopImages images; + + public MiniDesktopManager(boolean fullscreen, boolean stacking) { + this.fullscreen = fullscreen; + this.stacking = stacking; + } + + public void init() { + Display.setAppName("Mini SWT Desktop"); + display = Display.getCurrent(); + if (display != null) + throw new IllegalStateException("Already a display " + display); + display = new Display(); + + if (display.getTouchEnabled()) { + System.out.println("Touch enabled."); + } + + images = new MiniDesktopImages(display); + + int toolBarSize = 48; + + if (isFullscreen()) { + rootShell = new Shell(display, SWT.NO_TRIM); + rootShell.setFullScreen(true); + Rectangle bounds = display.getBounds(); + rootShell.setLocation(0, 0); + rootShell.setSize(bounds.width, bounds.height); + } else { + rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE); + Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480); + rootShell.setSize(shellArea.width, shellArea.height); + rootShell.setText(Display.getAppName()); + rootShell.setImage(images.terminalIcon); + } + + rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false))); + Composite toolBarArea = new Composite(rootShell, SWT.NONE); + toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y)); + + ToolBar toolBar; + if (isFullscreen()) { + toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP); + toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER); + createDock(toolBar); + toolBarShell.pack(); + toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y)); + } else { + toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER); + createDock(toolBar); + toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + } + + if (isStacking()) { + tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM); + tabFolder.setLayout(noSpaceGridLayout(new GridLayout())); + tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION); + tabFolder.setSelectionBackground(selectionBackground); + + // background + Control background = createBackground(tabFolder); + CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE); + homeTabItem.setText("Home"); + homeTabItem.setImage(images.homeIcon); + homeTabItem.setControl(background); + tabFolder.setFocus(); + } else { + createBackground(rootShell); + } + + rootShell.open(); + // rootShell.layout(true, true); + + if (toolBarShell != null) { + int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2; + toolBarShell.setLocation(0, toolBarShellY); + toolBarShell.open(); + } + + long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); + System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms."); + } + + protected void createDock(ToolBar toolBar) { + // Terminal + addToolItem(toolBar, images.terminalIcon, "Terminal", () -> { + String url = System.getProperty("user.home"); + AppContext appContext = createAppParent(images.terminalIcon); + new MiniTerminal(appContext.getAppParent(), url) { + + @Override + protected void exitCalled() { + if (appContext.shell != null) + appContext.shell.dispose(); + if (appContext.tabItem != null) + appContext.tabItem.dispose(); + } + }; + String title; + try { + title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + title = System.getProperty("user.name") + "@localhost"; + } + if (appContext.shell != null) + appContext.shell.setText(title); + if (appContext.tabItem != null) { + appContext.tabItem.setText(tabTitle(title)); + appContext.tabItem.setToolTipText(title); + } + openApp(appContext); + }); + + // Web browser + addToolItem(toolBar, images.browserIcon, "Browser", () -> { + String url = "https://start.duckduckgo.com/"; + AppContext appContext = createAppParent(images.browserIcon); + new MiniBrowser(appContext.getAppParent(), url, false, false) { + @Override + public void titleChanged(String title) { + if (appContext.shell != null) + appContext.shell.setText(title); + if (appContext.tabItem != null) { + appContext.tabItem.setText(tabTitle(title)); + appContext.tabItem.setToolTipText(title); + } + } + }; + openApp(appContext); + }); + + // File explorer + addToolItem(toolBar, images.explorerIcon, "Explorer", () -> { + String url = System.getProperty("user.home"); + AppContext appContext = createAppParent(images.explorerIcon); + new MiniExplorer(appContext.getAppParent(), url) { + + @Override + protected void pathChanged(Path path) { + if (appContext.shell != null) + appContext.shell.setText(path.toString()); + if (appContext.tabItem != null) { + appContext.tabItem.setText(path.getFileName().toString()); + appContext.tabItem.setToolTipText(path.toString()); + } + } + }; + openApp(appContext); + }); + + // Separator + new ToolItem(toolBar, SWT.SEPARATOR); + + // Exit + addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose()); + + toolBar.pack(); + } + + protected String tabTitle(String title) { + return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title; + } + + protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) { + ToolItem searchI = new ToolItem(toolBar, SWT.PUSH); + searchI.setImage(icon); + searchI.setToolTipText(name); + searchI.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + action.run(); + } + + }); + } + + protected AppContext createAppParent(Image icon) { + if (isStacking()) { + Composite appParent = new Composite(tabFolder, SWT.CLOSE); + appParent.setLayout(noSpaceGridLayout(new GridLayout())); + CTabItem item = new CTabItem(tabFolder, SWT.CLOSE); + item.setImage(icon); + item.setControl(appParent); + return new AppContext(item); + } else { + Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM) + : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM); + shell.setImage(icon); + return new AppContext(shell); + } + } + + protected void openApp(AppContext appContext) { + if (appContext.shell != null) { + Shell shell = (Shell) appContext.shell; + shell.open(); + shell.setSize(new Point(800, 480)); + } + if (appContext.tabItem != null) { + tabFolder.setFocus(); + tabFolder.setSelection(appContext.tabItem); + } + } + + protected Control createBackground(Composite parent) { + Composite backgroundArea = new Composite(parent, SWT.NONE); + backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + initBackground(backgroundArea); + return backgroundArea; + } + + protected void initBackground(Composite backgroundArea) { + MiniHomePart homePart = new MiniHomePart() { + + @Override + protected void fillAppsToolBar(ToolBar toolBar) { + createDock(toolBar); + } + }; + homePart.createUiPart(backgroundArea, null); + } + + public void run() { + while (!rootShell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + + public void dispose() { + if (!rootShell.isDisposed()) + rootShell.dispose(); + } + + protected boolean isFullscreen() { + return fullscreen; + } + + protected boolean isStacking() { + return stacking; + } + + protected Image getIconForExt(String ext) { +// Program program = Program.findProgram(ext); +// if (program == null) + return display.getSystemImage(SWT.ICON_INFORMATION); + +// ImageData iconData = program.getImageData(); +// if (iconData == null) { +// return display.getSystemImage(SWT.ICON_INFORMATION); +// } else { +// return new Image(display, iconData); +// } + + } + + private static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static void main(String[] args) { + List options = Arrays.asList(args); + if (options.contains("--help")) { + System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]"); + System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT."); + System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)"); + System.out.println(" --stacking : open apps as tabs (default is to create new windows)"); + System.out.println(" --help : print this help and exit"); + System.exit(1); + } + boolean fullscreen = options.contains("--fullscreen"); + boolean stacking = options.contains("--stacking"); + + MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking); + desktopManager.init(); + desktopManager.run(); + desktopManager.dispose(); + System.exit(0); + } + + class AppContext { + private Shell shell; + private CTabItem tabItem; + + public AppContext(Shell shell) { + this.shell = shell; + } + + public AppContext(CTabItem tabItem) { + this.tabItem = tabItem; + } + + Composite getAppParent() { + if (shell != null) + return shell; + if (tabItem != null) + return (Composite) tabItem.getControl(); + throw new IllegalStateException(); + } + } +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopSpecific.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopSpecific.java new file mode 100644 index 000000000..c7488226f --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopSpecific.java @@ -0,0 +1,29 @@ +package org.argeo.minidesktop; + +import java.util.Objects; + +import org.eclipse.swt.browser.Browser; + +/** + * This customiser is available to all components, in order to be extended with + * low-level specific capabilities, which depend on the context (typically + * differences between RAP and RCP). It does nothing by default. + */ +public class MiniDesktopSpecific { + protected void addBrowserTitleListener(MiniBrowser miniBrowser, Browser browser) { + } + + protected void addBrowserOpenWindowListener(MiniBrowser miniBrowser, Browser browser) { + } + + private static MiniDesktopSpecific SINGLETON = new MiniDesktopSpecific(); + + public static void setMiniDesktopSpecific(MiniDesktopSpecific miniDesktopSpecific) { + Objects.requireNonNull(miniDesktopSpecific); + SINGLETON = miniDesktopSpecific; + } + + static MiniDesktopSpecific getMiniDesktopSpecific() { + return SINGLETON; + } +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java new file mode 100644 index 000000000..9a967a2d4 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java @@ -0,0 +1,164 @@ +package org.argeo.minidesktop; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; + +public class MiniExplorer { + private Path path; + private Text addressT; + private Table browser; + + private boolean showHidden = false; + + public MiniExplorer(Composite parent, String url) { + this(parent); + setUrl(url); + } + + public MiniExplorer(Composite parent) { + parent.setLayout(noSpaceGridLayout(new GridLayout())); + + Composite toolBar = new Composite(parent, SWT.NONE); + toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + toolBar.setLayout(new FillLayout()); + addressT = new Text(toolBar, SWT.SINGLE); + addressT.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + setUrl(addressT.getText().trim()); + } + }); + browser = createTable(parent, this.path); + + } + + public void setPath(Path url) { + this.path = url; + if (addressT != null) + addressT.setText(url.toString()); + if (browser != null) { + Composite parent = browser.getParent(); + browser.dispose(); + browser = createTable(parent, this.path); + parent.layout(true, true); + } + pathChanged(url); + } + + protected void pathChanged(Path path) { + + } + + protected Table createTable(Composite parent, Path path) { + Table table = new Table(parent, SWT.BORDER); + table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + table.addMouseListener(new MouseAdapter() { + + @Override + public void mouseDoubleClick(MouseEvent e) { + Point pt = new Point(e.x, e.y); + TableItem item = table.getItem(pt); + Path path = (Path) item.getData(); + if (Files.isDirectory(path)) { + setPath(path); + } else { + //Program.launch(path.toString()); + } + } + }); + + if (path != null) { + if (path.getParent() != null) { + TableItem parentTI = new TableItem(table, SWT.NONE); + parentTI.setText(".."); + parentTI.setData(path.getParent()); + } + + try { + // directories + DirectoryStream ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p)); + ds.forEach(p -> { + TableItem ti = new TableItem(table, SWT.NONE); + ti.setText(p.getFileName().toString() + "/"); + ti.setData(p); + }); + // files + ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p)); + ds.forEach(p -> { + TableItem ti = new TableItem(table, SWT.NONE); + ti.setText(p.getFileName().toString()); + ti.setData(p); + }); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + return table; + } + + protected boolean isShown(Path path) { + if (showHidden) + return true; + try { + return !Files.isHidden(path); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot check " + path, e); + } + } + + public void setUrl(String url) { + setPath(Paths.get(url)); + } + + private static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static void main(String[] args) { + Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + + String url = args.length > 0 ? args[0] : System.getProperty("user.home"); + new MiniExplorer(shell, url) { + + @Override + protected void pathChanged(Path path) { + shell.setText(path.toString()); + } + + }; + + shell.open(); + shell.setSize(new Point(800, 480)); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java new file mode 100644 index 000000000..877f64384 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java @@ -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 netInterfaces = null; + try { + netInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + label(hostGroup, "Interfaces: "); + label(hostGroup, e.getMessage()); + } + if (netInterfaces != null) + while (netInterfaces.hasMoreElements()) { + NetworkInterface netInterface = netInterfaces.nextElement(); + byte[] hardwareAddress = null; + try { + hardwareAddress = netInterface.getHardwareAddress(); + if (hardwareAddress != null) { + label(hostGroup, convertHardwareAddress(hardwareAddress)); + label(hostGroup, netInterface.getName()); + for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) { + label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress())); + label(hostGroup, Short.toString(addr.getNetworkPrefixLength())); + } + } + } catch (SocketException e) { + label(hostGroup, e.getMessage()); + } + } + + // Resources + Group resGroup = new Group(parent, SWT.NONE); + resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + resGroup.setText("Resources"); + resGroup.setLayout(new GridLayout(3, false)); + + Runtime runtime = Runtime.getRuntime(); + + String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB"; + label(resGroup, "Max Java memory: "); + label(resGroup, maxMemoryStr); + label(resGroup, "Java version: " + Runtime.version().toString()); + + label(resGroup, "Usable Java memory: "); + Label totalMemory = label(resGroup, maxMemoryStr); + ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH); + totalOnMax.setMaximum(100); + label(resGroup, "Used Java memory: "); + Label usedMemory = label(resGroup, maxMemoryStr); + ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH); + totalOnMax.setMaximum(100); + new Thread() { + @Override + public void run() { + while (!totalOnMax.isDisposed()) { + display.asyncExec(() -> { + if (totalOnMax.isDisposed()) + return; + totalOnMax.setSelection(javaTotalOnMaxPerct(runtime)); + usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime)); + totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB"); + usedMemory.setText( + Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB"); + }); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return; + } + } + } + }.start(); + return parent; + } + + protected void fillAppsToolBar(ToolBar toolBar) { + + } + + protected int javaUsedOnTotalPerct(Runtime runtime) { + return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory()); + } + + protected int javaTotalOnMaxPerct(Runtime runtime) { + return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory()); + } + + protected Label label(Composite parent, String text) { + Label label = new Label(parent, SWT.WRAP); + label.setText(text); + return label; + } + + protected String cleanHostAddress(String hostAddress) { + // remove % from Ipv6 addresses + int index = hostAddress.indexOf('%'); + if (index > 0) + return hostAddress.substring(0, index); + else + return hostAddress; + } + + protected String convertHardwareAddress(byte[] hardwareAddress) { + if (hardwareAddress == null) + return ""; + // from https://stackoverflow.com/a/2797498/7878010 + StringBuilder sb = new StringBuilder(18); + for (byte b : hardwareAddress) { + if (sb.length() > 0) + sb.append(':'); + sb.append(String.format("%02x", b).toUpperCase()); + } + return sb.toString(); + } +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java new file mode 100644 index 000000000..86ff53fee --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java @@ -0,0 +1,129 @@ +package org.argeo.minidesktop; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageLoader; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; + +public class MiniImageViewer implements PaintListener { + private URL url; + private Canvas area; + + private Image image; + + public MiniImageViewer(Composite parent, int style) { + parent.setLayout(new GridLayout()); + + Composite toolBar = new Composite(parent, SWT.NONE); + toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + toolBar.setLayout(new RowLayout()); + Button load = new Button(toolBar, SWT.FLAT); + load.setText("\u2191");// up arrow + load.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fileDialog = new FileDialog(area.getShell()); + String path = fileDialog.open(); + if (path != null) { + setUrl(path); + } + } + + }); + + area = new Canvas(parent, SWT.NONE); + area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + area.addPaintListener(this); + } + + protected void load(URL url) { + try { + ImageLoader imageLoader = new ImageLoader(); + ImageData[] data = imageLoader.load(url.openStream()); + image = new Image(area.getDisplay(), data[0]); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override + public void paintControl(PaintEvent e) { + e.gc.drawImage(image, 0, 0); + + } + + protected Path url2path(URL url) { + try { + Path path = Paths.get(url.toURI()); + return path; + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot convert " + url + " to uri", e); + } + } + + public void setUrl(URL url) { + this.url = url; + if (area != null) + load(this.url); + } + + public void setUrl(String url) { + try { + setUrl(new URL(url)); + } catch (MalformedURLException e) { + // try with http + try { + setUrl(new URL("file://" + url)); + return; + } catch (MalformedURLException e1) { + // nevermind... + } + throw new IllegalArgumentException("Cannot interpret URL " + url, e); + } + } + + public static void main(String[] args) { + Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + + MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE); + String url = args.length > 0 ? args[0] : ""; + if (!url.trim().equals("")) { + miniBrowser.setUrl(url); + shell.setText(url); + } else { + shell.setText("*"); + } + + shell.open(); + shell.setSize(new Point(800, 480)); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java new file mode 100644 index 000000000..cd1a9f224 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java @@ -0,0 +1,342 @@ +package org.argeo.minidesktop; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.StringTokenizer; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +public class MiniTerminal implements KeyListener, PaintListener { + + private Canvas area; +// private Caret caret; + + private StringBuffer buf = new StringBuffer(""); + private StringBuffer userInput = new StringBuffer(""); + private List history = new ArrayList<>(); + + private Point charExtent = null; + private int charsPerLine = 0; + private String[] lines = new String[0]; + private List 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 args = new ArrayList<>(); + while (st.hasMoreTokens()) + args.add(st.nextToken()); + if (args.size() == 0) { + displayPrompt(); + return; + } + + // change directory + if (args.get(0).equals("cd")) { + if (args.size() == 1) { + setPath(homeDir); + } else { + Path newPath = currentDir.resolve(args.get(1)); + if (!Files.exists(newPath) || !Files.isDirectory(newPath)) { + println(newPath + ": No such file or directory"); + return; + } + setPath(newPath); + } + displayPrompt(); + return; + } + // show current directory + else if (args.get(0).equals("pwd")) { + println(currentDir); + displayPrompt(); + return; + } + // exit + else if (args.get(0).equals("exit")) { + println("logout"); + exitCalled(); + return; + } + + ProcessBuilder pb = new ProcessBuilder(args); + pb.redirectErrorStream(true); + pb.directory(currentDir.toFile()); +// Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile()); + process = pb.start(); + + stdIn = process.getOutputStream(); + readOut = new Thread("MiniTerminal read out") { + @Override + public void run() { + running = true; + try (BufferedReader in = new BufferedReader( + new InputStreamReader(process.getInputStream(), charset))) { + String line = null; + while ((line = in.readLine()) != null) { + println(line); + } + } catch (IOException e) { + println(e.getMessage()); + } + stdIn = null; + displayPrompt(); + running = false; + readOut = null; + process = null; + } + }; + readOut.start(); + } catch (IOException e) { + println(e.getMessage()); + displayPrompt(); + } + } + + protected int linesForLogicalLine(char[] line) { + return line.length / charsPerLine + 1; + } + + protected void println(Object line) { + buf.append(line); + markLogicalLine(); + } + + protected void refreshLines(int charPerLine, int nbrOfLines) { + if (lines.length != nbrOfLines) { + lines = new String[nbrOfLines]; + Arrays.fill(lines, null); + } + if (this.charsPerLine != charPerLine) + this.charsPerLine = charPerLine; + + int currentLine = nbrOfLines - 1; + // current line + if (buf.length() > 0) { + lines[currentLine] = buf.toString(); + } else { + lines[currentLine] = ""; + } + currentLine--; + + logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) { + char[] logicalLine = logicalLines.get(i).toCharArray(); + int linesNeeded = linesForLogicalLine(logicalLine); + for (int j = linesNeeded - 1; j >= 0; j--) { + int from = j * charPerLine; + int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length); + lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to)); +// System.out.println("Set line " + currentLine + " to : " + lines[currentLine]); + currentLine--; + if (currentLine < 0) + break logicalLines; + } + } + } + + @Override + public void paintControl(PaintEvent e) { + GC gc = e.gc; + gc.setFont(mono); + if (charExtent == null) + charExtent = gc.textExtent("a"); + + Point areaSize = area.getSize(); + int charPerLine = areaSize.x / charExtent.x; + int nbrOfLines = areaSize.y / charExtent.y; + refreshLines(charPerLine, nbrOfLines); + + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (line != null) + gc.drawString(line, 0, i * charExtent.y); + } +// String toDraw = buf.toString(); +// gc.drawString(toDraw, 0, 0); +// area.setCaret(caret); + } + + protected void exitCalled() { + + } + + public void setPath(String path) { + this.currentDir = Paths.get(path); + } + + public void setPath(Path path) { + this.currentDir = path; + } + + public static void main(String[] args) { + Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + + String url = args.length > 0 ? args[0] : System.getProperty("user.home"); + new MiniTerminal(shell, url) { + + @Override + protected void exitCalled() { + shell.dispose(); + System.exit(0); + } + }; + + shell.open(); + shell.setSize(new Point(800, 480)); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java new file mode 100644 index 000000000..91cd19e90 --- /dev/null +++ b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java @@ -0,0 +1,161 @@ +package org.argeo.minidesktop; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class MiniTextEditor { + private URL url; + private Text text; + + public MiniTextEditor(Composite parent, int style) { + parent.setLayout(new GridLayout()); + + Composite toolBar = new Composite(parent, SWT.NONE); + toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + toolBar.setLayout(new RowLayout()); + Button load = new Button(toolBar, SWT.FLAT); + load.setText("\u2191");// up arrow + load.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fileDialog = new FileDialog(text.getShell()); + String path = fileDialog.open(); + if (path != null) { + setUrl(path); + } + } + + }); + + Button save = new Button(toolBar, SWT.FLAT); + save.setText("\u2193");// down arrow + // save.setText("\u1F609");// emoji + save.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + save(url); + } + + }); + + text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + protected void load(URL url) { + text.setText(""); + // TODO deal with encoding and binary data + try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { + String line = null; + while ((line = in.readLine()) != null) { + text.append(line + "\n"); + } + text.setEditable(true); + } catch (IOException e) { + if (e instanceof FileNotFoundException) { + Path path = url2path(url); + try { + Files.createFile(path); + load(url); + return; + } catch (IOException e1) { + e = e1; + } + } + text.setText(e.getMessage()); + text.setEditable(false); + e.printStackTrace(); + // throw new IllegalStateException("Cannot load " + url, e); + } + } + + protected Path url2path(URL url) { + try { + Path path = Paths.get(url.toURI()); + return path; + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot convert " + url + " to uri", e); + } + } + + protected void save(URL url) { + if (!url.getProtocol().equals("file")) + throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write"); + Path path = url2path(url); + try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) { + out.write(text.getText()); + } catch (IOException e) { + throw new IllegalStateException("Cannot save " + url + " to " + path, e); + } + } + + public void setUrl(URL url) { + this.url = url; + if (text != null) + load(url); + } + + public void setUrl(String url) { + try { + setUrl(new URL(url)); + } catch (MalformedURLException e) { + // try with http + try { + setUrl(new URL("file://" + url)); + return; + } catch (MalformedURLException e1) { + // nevermind... + } + throw new IllegalArgumentException("Cannot interpret URL " + url, e); + } + } + + public static void main(String[] args) { + Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent(); + Shell shell = new Shell(display, SWT.SHELL_TRIM); + + MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE); + String url = args.length > 0 ? args[0] : ""; + if (!url.trim().equals("")) { + miniBrowser.setUrl(url); + shell.setText(url); + } else { + shell.setText("*"); + } + + shell.open(); + shell.setSize(new Point(800, 480)); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + +} diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png new file mode 100644 index 000000000..55c614dfa Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png differ diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png new file mode 100644 index 000000000..54ecae20f Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png differ diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png new file mode 100644 index 000000000..eb2fc720b Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png differ diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png new file mode 100644 index 000000000..06d337a43 Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png differ diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png new file mode 100644 index 000000000..31e671c46 Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png differ diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png new file mode 100644 index 000000000..adb7c2cc2 Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png differ diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png new file mode 100644 index 000000000..4c9a16c0f Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png differ diff --git a/swt/rap/org.argeo.cms.e4.rap/.classpath b/swt/rap/org.argeo.cms.e4.rap/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/rap/org.argeo.cms.e4.rap/.project b/swt/rap/org.argeo.cms.e4.rap/.project new file mode 100644 index 000000000..40c9e013f --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.e4.rap + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore b/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rap/org.argeo.cms.e4.rap/bnd.bnd b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd new file mode 100644 index 000000000..6db081fc2 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd @@ -0,0 +1,9 @@ +Import-Package: \ +org.argeo.api.acr, \ +org.eclipse.swt,\ +org.eclipse.swt.graphics,\ +org.eclipse.e4.ui.workbench,\ +org.eclipse.rap.rwt.client,\ +org.eclipse.nebula.widgets.richtext;resolution:=optional,\ +org.eclipse.*;resolution:=optional,\ +* diff --git a/swt/rap/org.argeo.cms.e4.rap/build.properties b/swt/rap/org.argeo.cms.e4.rap/build.properties new file mode 100644 index 000000000..c58ea2178 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/ diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java new file mode 100644 index 000000000..81326f345 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java @@ -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 baseProperties = new HashMap(); + + private BundleContext bundleContext; + public final static String CONTEXT_NAME_PROPERTY = "contextName"; + private String contextName; + + /** + * To be overridden in order to add multiple entry points, directly or using + * {@link #addE4EntryPoint(Application, String, String, Map)}. + */ + protected void addEntryPoints(Application application) { + } + + public void configure(Application application) { + application.setExceptionHandler(new ExceptionHandler() { + + @Override + public void handleException(Throwable throwable) { + CmsFeedback.error("Unexpected RWT exception", throwable); + } + }); + + if (e4Xmi != null) {// backward compatibility + addE4EntryPoint(application, path, e4Xmi, getBaseProperties()); + } else { + addEntryPoints(application); + } + } + + protected Map getBaseProperties() { + return baseProperties; + } + +// protected void addEntryPoint(Application application, E4ApplicationConfig config, Map 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 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 customise(String pageTitle) { + Map 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 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 = ""; + } + } + + public void destroy(Map properties) { + + } +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java new file mode 100644 index 000000000..a5a32348e --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java @@ -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() { + + @Override + public Integer run() { + // SPNEGO + // HttpServletRequest request = RWT.getRequest(); + // String authorization = request.getHeader(HEADER_AUTHORIZATION); + // if (authorization == null || !authorization.startsWith("Negotiate")) { + // HttpServletResponse response = RWT.getResponse(); + // response.setStatus(401); + // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate"); + // response.setDateHeader("Date", System.currentTimeMillis()); + // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60 + // * 1000)); + // response.setHeader("Accept-Ranges", "bytes"); + // response.setHeader("Connection", "Keep-Alive"); + // response.setHeader("Keep-Alive", "timeout=5, max=97"); + // // response.setContentType("text/html; charset=UTF-8"); + // } + + JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class); + Integer exitCode = ep.createUI(); + jsExecutor.execute("location.reload()"); + return exitCode; + } + + }); + } + }; + return authEp; + } + + protected EntryPoint createEntryPoint() { + return super.create(); + } +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java new file mode 100644 index 000000000..cdd87fd3f --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java @@ -0,0 +1,193 @@ +package org.argeo.cms.e4.rap; + +import java.security.AccessController; +import java.util.UUID; +import java.util.concurrent.Callable; + +import javax.inject.Inject; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.SimpleSwtUxContext; +import org.argeo.cms.swt.acr.AcrSwtImageManager; +import org.argeo.cms.swt.auth.CmsLoginShell; +import org.argeo.cms.swt.dialogs.CmsFeedback; +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.e4.ui.workbench.UIEvents; +import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; +import org.eclipse.e4.ui.workbench.lifecycle.PreSave; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.client.service.BrowserNavigation; +import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; +import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; + +@SuppressWarnings("restriction") +public class CmsLoginLifecycle implements CmsView { + private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class); + + @Inject + private CmsContext cmsContext; + + private UxContext uxContext; + private CmsImageManager imageManager; + + private LoginContext loginContext; + private BrowserNavigation browserNavigation; + + private String state = null; + private String uid; + + @PostContextCreate + boolean login(final IEventBroker eventBroker) { + uid = UUID.randomUUID().toString(); + browserNavigation = RWT.getClient().getService(BrowserNavigation.class); + if (browserNavigation != null) + browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() { + private static final long serialVersionUID = -3668136623771902865L; + + @Override + public void navigated(BrowserNavigationEvent event) { + state = event.getState(); + if (uxContext != null)// is logged in + stateChanged(); + } + }); + + Subject subject = Subject.getSubject(AccessController.getContext()); + Display display = Display.getCurrent(); +// UiContext.setData(CmsView.KEY, this); + CmsLoginShell loginShell = new CmsLoginShell(this, cmsContext); + CmsSwtUtils.registerCmsView(loginShell.getShell(), this); + loginShell.setSubject(subject); + try { + // try pre-auth + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell); + loginContext.login(); + } catch (LoginException e) { + loginShell.createUi(); + loginShell.open(); + + while (!loginShell.getShell().isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + if (CurrentUser.getUsername(getSubject()) == null) + return false; + uxContext = new SimpleSwtUxContext(); + imageManager = (CmsImageManager) new AcrSwtImageManager(); + + eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() { + @Override + public void handleEvent(Event event) { + startupComplete(); + eventBroker.unsubscribe(this); + } + }); + + // lcs.changeApplicationLocale(Locale.FRENCH); + return true; + } + + @PreSave + void destroy() { + // logout(); + } + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public void navigateTo(String state) { + browserNavigation.pushState(state, state); + } + + @Override + public void authChange(LoginContext loginContext) { + if (loginContext == null) + throw new IllegalArgumentException("Login context cannot be null"); + // logout previous login context + // if (this.loginContext != null) + // try { + // this.loginContext.logout(); + // } catch (LoginException e1) { + // System.err.println("Could not log out: " + e1); + // } + this.loginContext = loginContext; + } + + @Override + public void logout() { + if (loginContext == null) + throw new IllegalStateException("Login context should not be null"); + try { + CurrentUser.logoutCmsSession(loginContext.getSubject()); + loginContext.logout(); + } catch (LoginException e) { + throw new IllegalStateException("Cannot log out", e); + } + } + + @Override + public void exception(Throwable e) { + String msg = "Unexpected exception in Eclipse 4 RAP"; + log.error(msg, e); + CmsFeedback.error(msg, e); + } + + @Override + public CmsImageManager getImageManager() { + return imageManager; + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + @Override + public String getUid() { + return uid; + } + + // CALLBACKS + protected void startupComplete() { + } + + protected void stateChanged() { + + } + + // GETTERS + protected BrowserNavigation getBrowserNavigation() { + return browserNavigation; + } + + protected String getState() { + return state; + } + + @Override + public T doAs(Callable action) { + throw new UnsupportedOperationException(); + } + +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java new file mode 100644 index 000000000..764234e18 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java @@ -0,0 +1,32 @@ +package org.argeo.cms.e4.rap; + +import java.util.Enumeration; + +import org.argeo.api.cms.CmsLog; +import org.eclipse.rap.rwt.application.Application; +import org.osgi.framework.Bundle; + +/** Simple RAP app which loads all e4xmi files. */ +public class SimpleRapE4App extends AbstractRapE4App { + private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class); + + private String baseE4xmi = "/e4xmi"; + + @Override + protected void addEntryPoints(Application application) { + Bundle bundle = getBundleContext().getBundle(); + Enumeration paths = bundle.getEntryPaths(baseE4xmi); + while (paths.hasMoreElements()) { + String p = paths.nextElement(); + if (p.endsWith(".e4xmi")) { + String e4xmiPath = bundle.getSymbolicName() + '/' + p; + // FIXME deal with base name + String name=null;// = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p)); + addE4EntryPoint(application, name, e4xmiPath, getBaseProperties()); + if (log.isDebugEnabled()) + log.debug("Registered " + e4xmiPath + " as " + getContextName() + name); + } + } + } + +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java new file mode 100644 index 000000000..1122f1922 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse 4 RAP specific extensions. */ +package org.argeo.cms.e4.rap; \ No newline at end of file diff --git a/swt/rap/org.argeo.cms.swt.rap/.classpath b/swt/rap/org.argeo.cms.swt.rap/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/rap/org.argeo.cms.swt.rap/.project b/swt/rap/org.argeo.cms.swt.rap/.project new file mode 100644 index 000000000..84a20aeb1 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.swt.rap + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/rap/org.argeo.cms.swt.rap/META-INF/.gitignore b/swt/rap/org.argeo.cms.swt.rap/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml b/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml new file mode 100644 index 000000000..f5edebcda --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/swt/rap/org.argeo.cms.swt.rap/bnd.bnd b/swt/rap/org.argeo.cms.swt.rap/bnd.bnd new file mode 100644 index 000000000..81178ebd3 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/bnd.bnd @@ -0,0 +1,10 @@ +Import-Package:\ +org.argeo.api.acr,\ +org.eclipse.swt,\ +org.argeo.eclipse.ui,\ +org.eclipse.swt.graphics,\ +javax.servlet.*;version="[3,5)",\ +* + +Service-Component: OSGI-INF/cmsWebAppFactory.xml + diff --git a/swt/rap/org.argeo.cms.swt.rap/build.properties b/swt/rap/org.argeo.cms.swt.rap/build.properties new file mode 100644 index 000000000..2416e522a --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/build.properties @@ -0,0 +1,7 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/cmsWebAppFactory.xml +source.. = src/ +additional.bundles = org.argeo.ext.slf4j,\ + org.slf4j.api diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/BundleResourceLoader.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/BundleResourceLoader.java new file mode 100644 index 000000000..ca93e625e --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/BundleResourceLoader.java @@ -0,0 +1,34 @@ +package org.argeo.cms.web; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.eclipse.rap.rwt.service.ResourceLoader; +import org.osgi.framework.Bundle; + +/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */ +public class BundleResourceLoader implements ResourceLoader { + private final Bundle bundle; + + public BundleResourceLoader(Bundle bundle) { + this.bundle = bundle; + } + + @Override + public InputStream getResourceAsStream(String resourceName) throws IOException { + URL res = bundle.getEntry(resourceName); + if (res == null) { + res = bundle.getResource(resourceName); + if (res == null) + throw new IllegalArgumentException( + "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName()); + } + return res.openStream(); + } + + public Bundle getBundle() { + return bundle; + } + +} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java new file mode 100644 index 000000000..102a4e103 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java @@ -0,0 +1,23 @@ +package org.argeo.cms.web; + +import java.io.IOException; +import java.io.InputStream; + +import org.argeo.api.cms.ux.CmsTheme; +import org.eclipse.rap.rwt.service.ResourceLoader; + +/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */ +public class CmsThemeResourceLoader implements ResourceLoader { + private final CmsTheme theme; + + public CmsThemeResourceLoader(CmsTheme theme) { + super(); + this.theme = theme; + } + + @Override + public InputStream getResourceAsStream(String resourceName) throws IOException { + return theme.getResourceAsStream(resourceName); + } + +} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java new file mode 100644 index 000000000..67fa5ceac --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java @@ -0,0 +1,170 @@ +package org.argeo.cms.web; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.argeo.api.cms.CmsApp; +import org.argeo.api.cms.CmsAppListener; +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsTheme; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.util.LangUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.Application.OperationMode; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; +import org.eclipse.rap.rwt.application.ExceptionHandler; +import org.eclipse.rap.rwt.client.WebClient; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** An RWT web app integrating with a {@link CmsApp}. */ +public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener { + private final static CmsLog log = CmsLog.getLog(CmsWebApp.class); + + private BundleContext bundleContext; + private CmsApp cmsApp; + + private CmsEventBus cmsEventBus; + + private ServiceRegistration 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 properties) { + this.bundleContext = bundleContext; + contextName = properties.get(CONTEXT_NAME); + if (cmsApp != null) { + if (cmsApp.allThemesAvailable()) + publishWebApp(); + } + } + + public void destroy(BundleContext bundleContext, Map 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 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 commonProperties) { + for (String uiName : cmsApp.getUiNames()) { + Map 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 imagePaths = theme.getImagesPaths(); + if (imagePaths.contains(FAVICON_PNG)) { + properties.put(WebClient.FAVICON, FAVICON_PNG); + } + } else { + properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID); + } + String entryPointName = !uiName.equals("") ? "/" + uiName : "/"; + application.addEntryPoint(entryPointName, () -> { + CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName); + return entryPoint; + }, properties); + if (log.isDebugEnabled()) + log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName); + } +// if (log.isDebugEnabled()) +// log.debug("Published CMS web app /" + (contextName != null ? contextName : "")); + } + + public CmsApp getCmsApp() { + return cmsApp; + } + + BundleContext getBundleContext() { + return bundleContext; + } + + public void setCmsApp(CmsApp cmsApp) { + this.cmsApp = cmsApp; +// this.cmsAppId = properties.get(Constants.SERVICE_PID); + this.cmsApp.addCmsAppListener(this); + } + + public void unsetCmsApp(CmsApp cmsApp, Map properties) { + String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + if (!contextName.equals(this.contextName)) + return; + if (this.cmsApp != null) { + this.cmsApp.removeCmsAppListener(this); + } + if (rwtAppReg != null) + rwtAppReg.unregister(); + this.cmsApp = null; + } + + @Override + public void themingUpdated() { + if (cmsApp != null && cmsApp.allThemesAvailable()) + publishWebApp(); + } + + protected void publishWebApp() { + Dictionary regProps = LangUtils.dict(CONTEXT_NAME, contextName); + if (rwtAppReg != null) + rwtAppReg.unregister(); + if (bundleContext != null) { + rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps); + if (log.isDebugEnabled()) + log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ..."); + } + } + + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; + } + + public CmsEventBus getCmsEventBus() { + return cmsEventBus; + } + + public void setContextName(String contextName) { + this.contextName = contextName; + } + +} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java new file mode 100644 index 000000000..70f49f670 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -0,0 +1,312 @@ +package org.argeo.cms.web; + +import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext; + +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsApp; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.LocaleUtils; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; +import org.argeo.cms.swt.AbstractSwtCmsView; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.SimpleSwtUxContext; +import org.argeo.cms.swt.acr.AcrSwtImageManager; +import org.argeo.cms.swt.dialogs.CmsFeedback; +import org.argeo.eclipse.ui.specific.UiContext; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.application.EntryPoint; +import org.eclipse.rap.rwt.client.service.BrowserNavigation; +import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; +import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; +import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle; +import org.eclipse.rap.rwt.service.ServerPushSession; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTError; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** The {@link CmsView} for a {@link CmsWebApp}. */ +@SuppressWarnings("restriction") +public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, CmsView, BrowserNavigationListener { + private static final long serialVersionUID = 7733510691684570402L; + private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class); + + private final CmsWebApp cmsWebApp; + + // Client services + // private final JavaScriptExecutor jsExecutor; + private final BrowserNavigation browserNavigation; + + /** Experimental OS-like multi windows. */ + private boolean multipleShells = false; + + private ServerPushSession serverPushSession; + + public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) { + super(uiName); + assert cmsWebApp != null; + assert uiName != null; + this.cmsWebApp = cmsWebApp; + uid = UUID.randomUUID().toString(); + + // Initial login + LoginContext lc; + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, + new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()), + new ServletHttpResponse(UiContext.getHttpResponse()))); + lc.login(); + } catch (LoginException e) { + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, + new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()), + new ServletHttpResponse(UiContext.getHttpResponse()))); + lc.login(); + } catch (LoginException e1) { + throw new IllegalStateException("Cannot log in as anonymous", e1); + } + } + authChange(lc); + + // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class); + browserNavigation = RWT.getClient().getService(BrowserNavigation.class); + if (browserNavigation != null) + browserNavigation.addBrowserNavigationListener(this); + + } + + protected void createContents(Composite parent) { + Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + @Override + public Void run() { + try { + uxContext = new SimpleSwtUxContext(); + imageManager = (CmsImageManager) new AcrSwtImageManager(); + CmsSession cmsSession = getCmsSession(); + if (cmsSession != null) { + UiContext.setLocale(cmsSession.getLocale()); + LocaleUtils.setThreadLocale(cmsSession.getLocale()); + } else { + Locale rwtLocale = RWT.getUISession().getLocale(); + LocaleUtils.setThreadLocale(rwtLocale); + } + parent.setData(CmsApp.UI_NAME_PROPERTY, uiName); + display = parent.getDisplay(); + ui = cmsWebApp.getCmsApp().initUi(parent); + if (ui instanceof Composite) + ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll()); + serverPushSession = new ServerPushSession(); + + // required in order to doAs to work + // TODO check whether it would be worth optimising + serverPushSession.start(); + // we need ui to be set before refresh so that CmsView can store UI context data + // in it. + cmsWebApp.getCmsApp().refreshUi(ui, null); + } catch (Exception e) { + throw new IllegalStateException("Cannot create entrypoint contents", e); + } + return null; + } + }); + } + + @Override + public synchronized void logout() { + if (loginContext == null) + throw new IllegalArgumentException("Login context should not be null"); + try { + CurrentUser.logoutCmsSession(loginContext.getSubject()); + loginContext.logout(); + LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, + new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()), + new ServletHttpResponse(UiContext.getHttpResponse()))); + anonymousLc.login(); + authChange(anonymousLc); + } catch (LoginException e) { + log.error("Cannot logout", e); + } + } + + @Override + public synchronized void authChange(LoginContext lc) { + if (lc == null) + throw new IllegalArgumentException("Login context cannot be null"); + // logout previous login context + if (this.loginContext != null) + try { + this.loginContext.logout(); + } catch (LoginException e1) { + log.warn("Could not log out: " + e1); + } + this.loginContext = lc; + doRefresh(); + } + + @Override + public void exception(final Throwable e) { + if (e instanceof SWTError) { + SWTError swtError = (SWTError) e; + if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) + return; + } + display.syncExec(() -> { + // TODO internationalise + CmsFeedback.error("Unexpected exception", e); + // TODO report +// doRefresh(); + }); + } + + protected synchronized void doRefresh() { + if (ui != null) + Subject.doAs(getSubject(), new PrivilegedAction() { + @Override + public Void run() { +// if (exception != null) { +// // TODO internationalise +// CmsFeedback.error("Unexpected exception", exception); +// exception = null; +// // TODO report +// } + cmsWebApp.getCmsApp().refreshUi(ui, state); + return null; + } + }); + } + + /** Sets the state of the entry point and retrieve the related content. */ + protected String setState(String newState) { + cmsWebApp.getCmsApp().setState(ui, newState); + state = newState; + return null; + } + + @Override + public void navigateTo(String state) { +// exception = null; + String title = setState(state); + if (title != null) + doRefresh(); + if (browserNavigation != null) + browserNavigation.pushState(state, title); + } + + public CmsImageManager getImageManager() { + return imageManager; + } + + @Override + public void navigated(BrowserNavigationEvent event) { + setState(event.getState()); + // doRefresh(); + } + + @Override + public CmsEventBus getCmsEventBus() { + return cmsWebApp.getCmsEventBus(); + } + + @Override + public CmsApp getCmsApp() { + return cmsWebApp.getCmsApp(); + } + + @Override + public void stateChanged(String state, String title) { + browserNavigation.pushState(state, title); + } + + @Override + public CmsSession getCmsSession() { + CmsSession cmsSession = cmsWebApp.getCmsApp().getCmsContext().getCmsSession(getSubject()); + if (cmsSession == null) + throw new IllegalStateException("No CMS session available for " + getSubject()); + return cmsSession; + } + + /* + * EntryPoint IMPLEMENTATION + */ + + @Override + public int createUI() { + Display display = new Display(); + Shell shell = createShell(display); + shell.setLayout(CmsSwtUtils.noSpaceGridLayout()); + CmsSwtUtils.registerCmsView(shell, this); + createContents(shell); + shell.layout(); +// if (shell.getMaximized()) { +// shell.layout(); +// } else { +//// shell.pack(); +// } + shell.open(); + if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) { + eventLoop: while (!shell.isDisposed()) { + try { + Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + @Override + public Void run() { + if (!display.readAndDispatch()) { + display.sleep(); + } + return null; + } + }); + } catch (Throwable e) { + if (e instanceof SWTError) { + SWTError swtError = (SWTError) e; + if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) { + log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage()); + continue eventLoop; + } else { + log.error("Unexpected SWT error in event loop, shutting down...", e); + break eventLoop; + } + } else if (e instanceof ThreadDeath) { + throw (ThreadDeath) e; +// } else if (e instanceof Error) { +// log.error("Unexpected error in event loop, shutting down...", e); +// break eventLoop; + } else { + log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage()); + continue eventLoop; + } + } + } + if (!display.isDisposed()) + display.dispose(); + } + return 0; + } + + protected Shell createShell(Display display) { + Shell shell; + if (!multipleShells) { + shell = new Shell(display, SWT.NO_TRIM); + shell.setMaximized(true); + } else { + shell = new Shell(display, SWT.SHELL_TRIM); + shell.setSize(800, 600); + } + return shell; + } +} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/MinimalWebApp.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/MinimalWebApp.java new file mode 100644 index 000000000..2eff71ee8 --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/MinimalWebApp.java @@ -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 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 properties) { + + } + + @Override + public void configure(Application application) { + if (theme != null) + WebThemeUtils.apply(application, theme); + + Map properties = new HashMap<>(); + if (theme != null) { + properties.put(WebClient.THEME_ID, theme.getThemeId()); + properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders()); + } else { + properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID); + } + addEntryPoints(application, properties); + + } + + public void setTheme(BundleCmsTheme theme) { + this.theme = theme; + } + +} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/WebThemeUtils.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/WebThemeUtils.java new file mode 100644 index 000000000..e51644b9f --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/WebThemeUtils.java @@ -0,0 +1,28 @@ +package org.argeo.cms.web; + +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsTheme; +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.service.ResourceLoader; + +/** Web specific utilities around theming. */ +public class WebThemeUtils { + private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class); + + public static void apply(Application application, CmsTheme theme) { + ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme); + resources: for (String path : theme.getImagesPaths()) { + if (path.startsWith("target/")) + continue resources; // skip maven output + application.addResource(path, resourceLoader); + if (log.isTraceEnabled()) + log.trace("Theme " + theme.getThemeId() + ": added resource " + path); + } + for (String path : theme.getRapCssPaths()) { + application.addStyleSheet(theme.getThemeId(), path, resourceLoader); + if (log.isDebugEnabled()) + log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path); + } + } + +} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java new file mode 100644 index 000000000..83a83e2ad --- /dev/null +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java @@ -0,0 +1,55 @@ +package org.argeo.cms.web.osgi; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.argeo.api.cms.CmsApp; +import org.argeo.api.cms.CmsEventBus; +import org.argeo.cms.web.CmsWebApp; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** Publish a CmsApp as a RAP application. */ +public class CmsWebAppFactory { + private BundleContext bundleContext = FrameworkUtil.getBundle(CmsWebAppFactory.class).getBundleContext(); + private final static String CONTEXT_NAME = "contextName"; + + private CmsEventBus cmsEventBus; + + private Map registrations = Collections.synchronizedMap(new HashMap<>()); + + public void addCmsApp(CmsApp cmsApp, Map properties) { + String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + if (contextName != null) { + CmsWebApp cmsWebApp = new CmsWebApp(); + cmsWebApp.setCmsEventBus(cmsEventBus); + cmsWebApp.setCmsApp(cmsApp); + Hashtable serviceProperties = new Hashtable<>(); + if (!contextName.equals("")) + serviceProperties.put(CONTEXT_NAME, contextName); + cmsWebApp.init(bundleContext, serviceProperties); + registrations.put(contextName, cmsWebApp); + } + } + + public void removeCmsApp(CmsApp cmsApp, Map properties) { + String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + if (contextName != null) { + CmsWebApp cmsWebApp = registrations.get(contextName); + if (cmsWebApp != null) { + cmsWebApp.destroy(bundleContext, new HashMap<>()); + cmsWebApp.unsetCmsApp(cmsApp, properties); + } else { + // TODO log warning + } + } + } + + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; + } + + +} diff --git a/swt/rap/org.argeo.swt.specific.rap/.classpath b/swt/rap/org.argeo.swt.specific.rap/.classpath new file mode 100644 index 000000000..248abe0e1 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/.classpath @@ -0,0 +1,9 @@ + + + + + + + diff --git a/swt/rap/org.argeo.swt.specific.rap/.project b/swt/rap/org.argeo.swt.specific.rap/.project new file mode 100644 index 000000000..53d797685 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/.project @@ -0,0 +1,28 @@ + + + org.argeo.swt.specific.rap + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/rap/org.argeo.swt.specific.rap/META-INF/.gitignore b/swt/rap/org.argeo.swt.specific.rap/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rap/org.argeo.swt.specific.rap/bnd.bnd b/swt/rap/org.argeo.swt.specific.rap/bnd.bnd new file mode 100644 index 000000000..bcd9b195f --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/bnd.bnd @@ -0,0 +1,5 @@ +Import-Package: org.eclipse.swt,\ +org.eclipse.jface.dialogs,\ +org.eclipse.swt.events,\ +javax.servlet.http;version="[3,5)",\ +* diff --git a/swt/rap/org.argeo.swt.specific.rap/build.properties b/swt/rap/org.argeo.swt.specific.rap/build.properties new file mode 100644 index 000000000..fd806ca05 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/build.properties @@ -0,0 +1,2 @@ +source.. = src/ +output.. = bin/ diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java new file mode 100644 index 000000000..ac4e0dfb1 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java @@ -0,0 +1,20 @@ +package org.argeo.eclipse.ui.specific; + +import java.awt.image.BufferedImage; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +public class BufferedImageDisplay extends Composite { + private static final long serialVersionUID = 4541163690514461514L; + private BufferedImage image; + + public BufferedImageDisplay(Composite parent, int style) { + super(parent, SWT.NO_BACKGROUND); + } + + public void setImage(BufferedImage image) { + this.image = image; + } + +} diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java new file mode 100644 index 000000000..6100c1a83 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java @@ -0,0 +1,17 @@ +package org.argeo.eclipse.ui.specific; + +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; + +public class CmsFileDialog extends FileDialog { + private static final long serialVersionUID = -7540791204102318801L; + + public CmsFileDialog(Shell parent, int style) { + super(parent, style); + } + + public CmsFileDialog(Shell parent) { + super(parent); + } + +} diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java new file mode 100644 index 000000000..3f30bde25 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java @@ -0,0 +1,34 @@ +package org.argeo.eclipse.ui.specific; + +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Composite; + +public class CmsFileUpload extends FileUpload { + private static final long serialVersionUID = 8027963992680980549L; + + public CmsFileUpload(Composite parent, int style) { + super(parent, style); + } + + @Override + public void setText(String text) { + super.setText(text); + } + + @Override + public String getFileName() { + return super.getFileName(); + } + + @Override + public String[] getFileNames() { + return super.getFileNames(); + } + + @Override + public void addSelectionListener(SelectionListener listener) { + super.addSelectionListener(listener); + } + +} diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java new file mode 100644 index 000000000..a89b921cd --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java @@ -0,0 +1,39 @@ +package org.argeo.eclipse.ui.specific; + +import org.eclipse.jface.viewers.AbstractTableViewer; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.widgets.Widget; + +/** Static utilities to bridge differences between RCP and RAP */ +public class EclipseUiSpecificUtils { + + public static void setStyleData(Widget widget, Object data) { + widget.setData(RWT.CUSTOM_VARIANT, data); + } + + public static Object getStyleData(Widget widget) { + return widget.getData(RWT.CUSTOM_VARIANT); + } + + public static void setMarkupData(Widget widget) { + widget.setData(RWT.MARKUP_ENABLED, true); + } + + public static void setMarkupValidationDisabledData(Widget widget) { + widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE); + } + + /** + * TootlTip support is supported only for {@link AbstractTableViewer} in RAP + */ + public static void enableToolTipSupport(Viewer viewer) { + if (viewer instanceof ColumnViewer) + ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer); + } + + private EclipseUiSpecificUtils() { + } +} diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java new file mode 100644 index 000000000..f9ca81682 --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java @@ -0,0 +1,73 @@ +package org.argeo.eclipse.ui.specific; + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadReceiver; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.client.ClientFile; +import org.eclipse.rap.rwt.client.service.ClientFileUploader; +import org.eclipse.rap.rwt.dnd.ClientFileTransfer; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetAdapter; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.widgets.Control; + +/** Configures a {@link Control} to receive files drop from the client OS. */ +public class FileDropAdapter { + + public void prepareDropTarget(Control control, DropTarget dropTarget) { + dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() }); + dropTarget.addDropListener(new DropTargetAdapter() { + private static final long serialVersionUID = 5361645765549463168L; + + @Override + public void dropAccept(DropTargetEvent event) { + if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) { + event.detail = DND.DROP_NONE; + } + } + + @Override + public void drop(DropTargetEvent event) { + handleFileDrop(control, event); + } + }); + } + + public void handleFileDrop(Control control, DropTargetEvent event) { + ClientFile[] clientFiles = (ClientFile[]) event.data; + ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class); +// DiskFileUploadReceiver receiver = new DiskFileUploadReceiver(); + FileUploadReceiver receiver = new FileUploadReceiver() { + + @Override + public void receive(InputStream stream, FileDetails details) throws IOException { + control.getDisplay().syncExec(() -> { + try { + processUpload(stream, details.getFileName(), details.getContentType()); + } catch (IOException e) { + throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e); + } + }); + } + }; + FileUploadHandler handler = new FileUploadHandler(receiver); +// handler.setMaxFileSize( sizeLimit ); +// handler.setUploadTimeLimit( timeLimit ); + service.submit(handler.getUploadUrl(), clientFiles); +// for (File file : receiver.getTargetFiles()) { +// paths.add(file.toPath()); +// } + } + + /** Executed in UI thread */ + protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { + + } + +} diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java new file mode 100644 index 000000000..72e17a22d --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java @@ -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 getData(String key) { + Display display = getDisplay(); + if (display == null) + return null; + return (T) display.getData(key); + } + + public static void setData(String key, Object value) { + Display display = getDisplay(); + if (display == null) + throw new IllegalStateException("Not display available"); + display.setData(key, value); + } + + private static Display getDisplay() { + return Display.getCurrent(); + } + + private UiContext() { + } + +} diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java new file mode 100644 index 000000000..4ec451f8a --- /dev/null +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */ +package org.argeo.eclipse.ui.specific; \ No newline at end of file diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.classpath b/swt/rcp/org.argeo.cms.e4.rcp/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.gitignore b/swt/rcp/org.argeo.cms.e4.rcp/.gitignore new file mode 100644 index 000000000..710cd6893 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/target/ +/exec diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.project b/swt/rcp/org.argeo.cms.e4.rcp/.project new file mode 100644 index 000000000..64d561913 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.e4.rcp + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0c68a61dc --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..f29e940a0 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi new file mode 100644 index 000000000..6743a4e07 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi @@ -0,0 +1,26 @@ + + + + + + + + + + + + editorArea + + + + + + + + + + + + + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties new file mode 100644 index 000000000..0a0da7581 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties @@ -0,0 +1,23 @@ +argeo.osgi.start.2.node=\ +org.eclipse.equinox.http.servlet,\ +org.eclipse.equinox.metatype,\ +org.eclipse.equinox.cm,\ +org.argeo.init + +argeo.osgi.start.3.node=\ +org.argeo.cms,\ +org.argeo.cms.jcr,\ + +applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi +lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle +clearPersistedState=true +#argeo.cms.desktop.inTray=true + +# Remote node: +#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node + +# Logging +log.org.argeo=DEBUG + +argeo.node.useradmin.uris=os:/// +eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application diff --git a/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd new file mode 100644 index 000000000..eaa51adec --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd @@ -0,0 +1,8 @@ +Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true + +Require-Bundle: org.eclipse.core.runtime + +Import-Package: !org.eclipse.core.runtime,\ +org.eclipse.swt,\ +org.eclipse.*;resolution:=optional,\ +* diff --git a/swt/rcp/org.argeo.cms.e4.rcp/build.properties b/swt/rcp/org.argeo.cms.e4.rcp/build.properties new file mode 100644 index 000000000..355413e4f --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/build.properties @@ -0,0 +1,5 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + argeo-companion.e4xmi +source.. = src/ diff --git a/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties b/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties new file mode 100644 index 000000000..13f949ff5 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties @@ -0,0 +1,32 @@ +log4j.rootLogger=WARN, development + +## Levels +log4j.logger.org.argeo=DEBUG +log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN +log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO + +#log4j.logger.org.springframework.security=DEBUG +#log4j.logger.org.apache.commons.exec=DEBUG +#log4j.logger.org.apache.jackrabbit.webdav=DEBUG +#log4j.logger.org.apache.jackrabbit.remote=DEBUG +#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG + +log4j.logger.org.apache.catalina=INFO +log4j.logger.org.apache.coyote=INFO + +log4j.logger.org.apache.directory=INFO +log4j.logger.org.apache.directory.server=ERROR +log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR + +## Appenders +# console is set to be a ConsoleAppender. +log4j.appender.console=org.apache.log4j.ConsoleAppender + +# console uses PatternLayout. +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n + +# development appender (slow!) +log4j.appender.development=org.apache.log4j.ConsoleAppender +log4j.appender.development.layout=org.apache.log4j.PatternLayout +log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n diff --git a/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml b/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml new file mode 100644 index 000000000..3e6896beb --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java new file mode 100644 index 000000000..3861597aa --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java @@ -0,0 +1,212 @@ +package org.argeo.cms.e4.rcp; + +import java.security.PrivilegedExceptionAction; +import java.util.UUID; +import java.util.concurrent.Callable; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.SimpleSwtUxContext; +import org.argeo.cms.swt.auth.CmsLoginShell; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.Platform; +import org.eclipse.equinox.app.IApplication; +import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.swt.widgets.Display; + +public class CmsE4Application implements IApplication, CmsView { + private LoginContext loginContext; + private IApplication e4Application; + private UxContext uxContext; + private String uid; + + @Override + public Object start(IApplicationContext context) throws Exception { + // TODO wait for CMS to be ready + Thread.sleep(5000); + + uid = UUID.randomUUID().toString(); + Subject subject = new Subject(); + Display display = createDisplay(); + CmsLoginShell loginShell = new CmsLoginShell(this, null); + // TODO customize CmsLoginShell to be smaller and centered + loginShell.setSubject(subject); + try { + // try pre-auth + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell); + loginContext.login(); + } catch (LoginException e) { + e.printStackTrace(); + loginShell.createUi(); + loginShell.open(); + + while (!loginShell.getShell().isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + if (CurrentUser.getUsername(getSubject()) == null) + throw new IllegalStateException("Cannot log in"); + + // try { + // CallbackHandler callbackHandler = new DefaultLoginDialog( + // display.getActiveShell()); + // loginContext = new LoginContext( + // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject, + // callbackHandler); + // } catch (LoginException e1) { + // throw new CmsException("Cannot initialize login context", e1); + // } + // + // // login + // try { + // loginContext.login(); + // subject = loginContext.getSubject(); + // } catch (LoginException e) { + // e.printStackTrace(); + // display.dispose(); + // try { + // Thread.sleep(2000); + // } catch (InterruptedException e1) { + // // silent + // } + // return null; + // } + + uxContext = new SimpleSwtUxContext(); + // UiContext.setData(CmsView.KEY, this); + CmsSwtUtils.registerCmsView(loginShell.getShell(), this); + e4Application = getApplication(null); + Object res = Subject.doAs(subject, new PrivilegedExceptionAction() { + + @Override + public Object run() throws Exception { + return e4Application.start(context); + } + + }); + return res; + } + + @Override + public void stop() { + if (e4Application != null) + e4Application.stop(); + } + + static IApplication getApplication(String[] args) { + IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME, + Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application"); + try { + IConfigurationElement[] elements = extension.getConfigurationElements(); + if (elements.length > 0) { + IConfigurationElement[] runs = elements[0].getChildren("run"); + if (runs.length > 0) { + Object runnable; + runnable = runs[0].createExecutableExtension("class"); + if (runnable instanceof IApplication) + return (IApplication) runnable; + } + } + } catch (Exception e) { + throw new IllegalStateException("Cannot find e4 application", e); + } + throw new IllegalStateException("Cannot find e4 application"); + } + + public static Display createDisplay() { + Display.setAppName("Argeo CMS RCP"); + + // create the display + Display newDisplay = Display.getCurrent(); + if (newDisplay == null) { + newDisplay = new Display(); + } + // Set the priority higher than normal so as to be higher + // than the JobManager. + Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1)); + return newDisplay; + } + + // + // CMS VIEW + // + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public void navigateTo(String state) { + // TODO Auto-generated method stub + + } + + @Override + public void authChange(LoginContext loginContext) { + if (loginContext == null) + throw new IllegalStateException("Login context cannot be null"); + // logout previous login context + // if (this.loginContext != null) + // try { + // this.loginContext.logout(); + // } catch (LoginException e1) { + // System.err.println("Could not log out: " + e1); + // } + this.loginContext = loginContext; + } + + @Override + public void logout() { + if (loginContext == null) + throw new IllegalStateException("Login context should not bet null"); + try { + CurrentUser.logoutCmsSession(loginContext.getSubject()); + loginContext.logout(); + } catch (LoginException e) { + throw new IllegalStateException("Cannot log out", e); + } + } + + @Override + public void exception(Throwable e) { + // TODO Auto-generated method stub + + } + + @Override + public CmsImageManager getImageManager() { + // TODO Auto-generated method stub + return null; + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + @Override + public String getUid() { + return uid; + } + + @Override + public T doAs(Callable action) { + throw new UnsupportedOperationException(); + } + +} diff --git a/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java new file mode 100644 index 000000000..1d38fe718 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java @@ -0,0 +1,27 @@ +package org.argeo.cms.e4.rcp; + +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; +import org.eclipse.e4.ui.workbench.lifecycle.PreSave; +import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions; +import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals; + +@SuppressWarnings("restriction") +public class CmsRcpLifeCycle { + + @PostContextCreate + void postContextCreate(IEclipseContext workbenchContext) { + } + + @PreSave + void preSave(IEclipseContext workbenchContext) { + } + + @ProcessAdditions + void processAdditions(IEclipseContext workbenchContext) { + } + + @ProcessRemovals + void processRemovals(IEclipseContext workbenchContext) { + } +} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/.classpath b/swt/rcp/org.argeo.cms.swt.rcp/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/.gitignore b/swt/rcp/org.argeo.cms.swt.rcp/.gitignore new file mode 100644 index 000000000..09e3bc9b2 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/target/ diff --git a/swt/rcp/org.argeo.cms.swt.rcp/.project b/swt/rcp/org.argeo.cms.swt.rcp/.project new file mode 100644 index 000000000..bcded59e6 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.swt.rcp + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.cms.swt.rcp/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml new file mode 100644 index 000000000..8b1d14684 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml @@ -0,0 +1,4 @@ + + + + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpHttpLauncher.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpHttpLauncher.xml new file mode 100644 index 000000000..03abe196c --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpHttpLauncher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd new file mode 100644 index 000000000..5cf5164e1 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd @@ -0,0 +1,13 @@ +Bundle-SymbolicName: org.argeo.cms.swt.rcp;singleton=true + +Import-Package:\ +org.argeo.cms.auth,\ +org.eclipse.swt,\ +org.eclipse.swt.graphics,\ +org.w3c.css.sac,\ +* + +Service-Component:\ +OSGI-INF/cmsRcpDisplayFactory.xml,\ +OSGI-INF/cmsRcpHttpLauncher.xml + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/build.properties b/swt/rcp/org.argeo.cms.swt.rcp/build.properties new file mode 100644 index 000000000..4ed1d4748 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/build.properties @@ -0,0 +1,6 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/,\ + OSGI-INF/cmsRcpHttpLauncher.xml +source.. = src/ diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java new file mode 100644 index 000000000..a88ff3824 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java @@ -0,0 +1,167 @@ +package org.argeo.cms.ui.rcp; + +import java.io.IOException; +import java.io.InputStream; +import java.security.PrivilegedAction; +import java.util.Map; +import java.util.UUID; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsApp; +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.ux.CmsTheme; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.cms.swt.AbstractSwtCmsView; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.e4.ui.css.core.engine.CSSEngine; +import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler; +import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** Runs a {@link CmsApp} as an SWT desktop application. */ +@SuppressWarnings("restriction") +public class CmsRcpApp extends AbstractSwtCmsView implements CmsView { + private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class); + + private Shell shell; + private CmsApp cmsApp; + + private CSSEngine cssEngine; + + public CmsRcpApp(String uiName) { + super(uiName); + uid = UUID.randomUUID().toString(); + } + + public void initRcpApp() { + display = Display.getCurrent(); + shell = new Shell(display); + shell.setText("Argeo CMS"); + Composite parent = shell; + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this); + + try { + loginContext = new LoginContext(CmsAuth.SINGLE_USER.getLoginContextName()); + loginContext.login(); + } catch (LoginException e) { + throw new IllegalStateException("Could not log in.", e); + } + if (log.isDebugEnabled()) + log.debug("Logged in to desktop: " + loginContext.getSubject()); + + Subject.doAs(loginContext.getSubject(), (PrivilegedAction) () -> { + + // TODO factorise with web app + parent.setData(CmsApp.UI_NAME_PROPERTY, uiName); + ui = cmsApp.initUi(parent); + if (ui instanceof Composite) + ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll()); + // we need ui to be set before refresh so that CmsView can store UI context data + // in it. + cmsApp.refreshUi(ui, null); + + // Styling + CmsTheme theme = CmsSwtUtils.getCmsTheme(parent); + if (theme != null) { + cssEngine = new CSSSWTEngineImpl(display); + for (String path : theme.getSwtCssPaths()) { + try (InputStream in = theme.loadPath(path)) { + cssEngine.parseStyleSheet(in); + } catch (IOException e) { + throw new IllegalStateException("Cannot load stylesheet " + path, e); + } + } + cssEngine.setErrorHandler(new CSSErrorHandler() { + public void error(Exception e) { + log.error("SWT styling error: ", e); + } + }); + applyStyles(shell); + } + shell.layout(true, true); + + shell.open(); + return null; + }); + } + + /* + * CMS VIEW + */ + + @Override + public void navigateTo(String state) { + throw new UnsupportedOperationException(); + } + + @Override + public void authChange(LoginContext loginContext) { + } + + @Override + public void logout() { + if (loginContext != null) + try { + loginContext.logout(); + } catch (LoginException e) { + log.error("Cannot log out", e); + } + } + + @Override + public void exception(Throwable e) { + log.error("Unexpected exception in CMS RCP", e); + } + + @Override + public CmsSession getCmsSession() { + CmsSession cmsSession = cmsApp.getCmsContext().getCmsSession(getSubject()); + return cmsSession; + } + + @Override + public boolean isAnonymous() { + return false; + } + + @Override + public void applyStyles(Object node) { + if (cssEngine != null) + cssEngine.applyStyles(node, true); + } + + public Shell getShell() { + return shell; + } + + @Override + public CmsEventBus getCmsEventBus() { + return cmsApp.getCmsContext().getCmsEventBus(); + } + + @Override + public CmsApp getCmsApp() { + return cmsApp; + } + + /* + * DEPENDENCY INJECTION + */ + public void setCmsApp(CmsApp cmsApp, Map properties) { + this.cmsApp = cmsApp; + } + + public void unsetCmsApp(CmsApp cmsApp, Map properties) { + this.cmsApp = null; + } + +} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java new file mode 100644 index 000000000..a83a54db3 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java @@ -0,0 +1,93 @@ +package org.argeo.cms.ui.rcp; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.nio.file.Path; + +import org.argeo.api.cms.CmsApp; +import org.argeo.cms.util.OS; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Display; + +/** Creates the SWT {@link Display} in a dedicated thread. */ +public class CmsRcpDisplayFactory { + private final static Logger logger = System.getLogger(CmsRcpDisplayFactory.class.getName()); + + /** File name in a run directory */ + private final static String ARGEO_RCP_URL = "argeo.rcp.url"; + + /** There is only one display in RCP mode */ + private static Display display; + + private CmsUiThread uiThread; + + private boolean shutdown = false; + + public void init() { + uiThread = new CmsUiThread(); + uiThread.start(); + while (display == null) + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // silent + } + } + + public void destroy() { + shutdown = true; + display.wake(); + try { + uiThread.join(); + } catch (InterruptedException e) { + // silent + } finally { + uiThread = null; + } + } + + class CmsUiThread extends Thread { + + public CmsUiThread() { + super("CMS UI"); + } + + @Override + public void run() { + try { + display = Display.getDefault(); + display.setRuntimeExceptionHandler((e) -> e.printStackTrace()); + display.setErrorHandler((e) -> e.printStackTrace()); + + while (!shutdown) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + display = null; + } catch (UnsatisfiedLinkError e) { + logger.log(Level.ERROR, + "Cannot load SWT, probably because the OSGi framework has been refresh. Restart the application.", + e); + } + } + } + + public static Display getDisplay() { + return display; + } + + public static void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) { + CmsRcpDisplayFactory.getDisplay().syncExec(() -> { + CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName); + cmsRcpApp.setCmsApp(cmsApp, null); + cmsRcpApp.initRcpApp(); + if (disposeListener != null) + cmsRcpApp.getShell().addDisposeListener(disposeListener); + }); + } + + public static Path getUrlRunFile() { + return OS.getRunDir().resolve(CmsRcpDisplayFactory.ARGEO_RCP_URL); + } +} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpHttpLauncher.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpHttpLauncher.java new file mode 100644 index 000000000..6246b0d0d --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpHttpLauncher.java @@ -0,0 +1,128 @@ +package org.argeo.cms.ui.rcp; + +import static java.lang.System.Logger.Level.DEBUG; + +import java.io.IOException; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.net.DatagramSocket; +import java.net.ServerSocket; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.argeo.api.cms.CmsApp; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +/** Publishes one {@link CmsRcpServlet} per {@link CmsApp}. */ +public class CmsRcpHttpLauncher { + private final static Logger logger = System.getLogger(CmsRcpHttpLauncher.class.getName()); + private CompletableFuture httpServer = new CompletableFuture<>(); + + public void init() { + + } + + public void destroy() { + Path runFile = CmsRcpDisplayFactory.getUrlRunFile(); + try { + if (Files.exists(runFile)) { + Files.delete(runFile); + } + } catch (IOException e) { + logger.log(Level.ERROR, "Cannot delete " + runFile, e); + } + } + + public void addCmsApp(CmsApp cmsApp, Map properties) { + final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + if (contextName != null) { + httpServer.thenAcceptAsync((httpServer) -> { + httpServer.createContext("/" + contextName, new HttpHandler() { + + @Override + public void handle(HttpExchange exchange) throws IOException { + String path = exchange.getRequestURI().getPath(); + String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : ""; + CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null); + exchange.sendResponseHeaders(200, -1); + logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App /" + contextName); + } + }); + }).exceptionally(e -> { + logger.log(Level.ERROR, "Cannot register RCO app " + contextName, e); + return null; + }); + logger.log(Level.DEBUG, "Registered RCP CMS APP /" + contextName); + } + } + + public void removeCmsApp(CmsApp cmsApp, Map properties) { + String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + if (contextName != null) { + httpServer.thenAcceptAsync((httpServer) -> { + httpServer.removeContext("/" + contextName); + }); + } + } + + public void setHttpServer(HttpServer httpServer) { + Integer httpPort = httpServer.getAddress().getPort(); + String baseUrl = "http://localhost:" + httpPort + "/"; + Path runFile = CmsRcpDisplayFactory.getUrlRunFile(); + try { + if (!Files.exists(runFile)) { + Files.createDirectories(runFile.getParent()); + // TODO give read permission only to the owner + Files.createFile(runFile); + } else { + URI uri = URI.create(Files.readString(runFile)); + if (!httpPort.equals(uri.getPort())) + if (!isPortAvailable(uri.getPort())) { + throw new IllegalStateException("Another CMS is running on " + runFile); + } else { + logger.log(Level.WARNING, + "Run file " + runFile + " found but port of " + uri + " is available. Overwriting..."); + } + } + Files.writeString(runFile, baseUrl, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Cannot write run file to " + runFile, e); + } + logger.log(DEBUG, "RCP available under " + baseUrl + ", written to " + runFile); + this.httpServer.complete(httpServer); + } + + protected boolean isPortAvailable(int port) { + ServerSocket ss = null; + DatagramSocket ds = null; + try { + ss = new ServerSocket(port); + ss.setReuseAddress(true); + ds = new DatagramSocket(port); + ds.setReuseAddress(true); + return true; + } catch (IOException e) { + } finally { + if (ds != null) { + ds.close(); + } + + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + /* should not be thrown */ + } + } + } + + return false; + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/.classpath b/swt/rcp/org.argeo.swt.specific.rcp/.classpath new file mode 100644 index 000000000..248abe0e1 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/.classpath @@ -0,0 +1,9 @@ + + + + + + + diff --git a/swt/rcp/org.argeo.swt.specific.rcp/.gitignore b/swt/rcp/org.argeo.swt.specific.rcp/.gitignore new file mode 100644 index 000000000..5e77890b2 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/.gitignore @@ -0,0 +1,3 @@ +/target/ +/bin/ +*.log \ No newline at end of file diff --git a/swt/rcp/org.argeo.swt.specific.rcp/.project b/swt/rcp/org.argeo.swt.specific.rcp/.project new file mode 100644 index 000000000..c79ee3fc9 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/.project @@ -0,0 +1,28 @@ + + + org.argeo.swt.specific.rcp + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/swt/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd b/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd new file mode 100644 index 000000000..bb88efda7 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd @@ -0,0 +1,20 @@ +Import-Package: \ +!java.*,\ +org.apache.commons.io,\ +org.eclipse.core.commands,\ +!org.eclipse.core.runtime,\ +!org.eclipse.ui.plugin,\ +org.eclipse.swt,\ +javax.servlet.http;version="[3,5)",\ +javax.servlet;version="[3,5)",\ +* + +Export-Package: org.argeo.*,\ +org.eclipse.rap.fileupload.*;version="3.10",\ +org.eclipse.rap.rwt.*;version="3.10" + +# Was !org.eclipse.core.commands,\ why ? + +#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin +#Bundle-ActivationPolicy: lazy +#Ignore-Package: org.eclipse.core.commands \ No newline at end of file diff --git a/swt/rcp/org.argeo.swt.specific.rcp/build.properties b/swt/rcp/org.argeo.swt.specific.rcp/build.properties new file mode 100644 index 000000000..c6b651a59 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/build.properties @@ -0,0 +1,3 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/ diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java new file mode 100644 index 000000000..0d9ce481d --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java @@ -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 getService(Class type) { + if (type.isAssignableFrom(JavaScriptExecutor.class)) + return (T) javaScriptExecutor; + else if (type.isAssignableFrom(BrowserNavigation.class)) + return (T) browserNavigation; + else + return null; + } + + private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() { + + @Override + public void execute(String code) { + // TODO Auto-generated method stub + + } + }; + private BrowserNavigation browserNavigation = new BrowserNavigation() { + + @Override + public void pushState(String state, String title) { + // TODO Auto-generated method stub + + } + + @Override + public void addBrowserNavigationListener( + BrowserNavigationListener listener) { + // TODO Auto-generated method stub + + } + }; +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java new file mode 100644 index 000000000..47ff35dc0 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java @@ -0,0 +1,45 @@ +package org.argeo.eclipse.ui.rcp.internal.rwt; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +import org.argeo.cms.util.StreamUtils; +import org.eclipse.rap.rwt.service.ResourceManager; + +public class RcpResourceManager implements ResourceManager { + private Map register = Collections.synchronizedMap(new TreeMap()); + + @Override + public void register(String name, InputStream in) { + try { + register.put(name, StreamUtils.toByteArray(in)); + } catch (IOException e) { + throw new RuntimeException("Cannot register " + name, e); + } + } + + @Override + public boolean unregister(String name) { + return register.remove(name) != null; + } + + @Override + public InputStream getRegisteredContent(String name) { + return new ByteArrayInputStream(register.get(name)); + } + + @Override + public String getLocation(String name) { + return name; + } + + @Override + public boolean isRegistered(String name) { + return register.containsKey(name); + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java new file mode 100644 index 000000000..7fd2db1d1 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java @@ -0,0 +1,38 @@ +package org.argeo.eclipse.ui.specific; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.image.BufferedImage; + +import javax.swing.JPanel; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.awt.SWT_AWT; +import org.eclipse.swt.widgets.Composite; + +public class BufferedImageDisplay extends Composite { + private BufferedImage image; + + public BufferedImageDisplay(Composite parent, int style) { + super(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND); + Frame frame = SWT_AWT.new_Frame(this); + frame.setLayout(new BorderLayout()); + frame.add(new JPanel() { + private static final long serialVersionUID = 8924410573598922364L; + + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (image != null) + g.drawImage(image, 0, 0, this); + } + + }, BorderLayout.CENTER); + frame.setVisible(true); + } + + public void setImage(BufferedImage image) { + this.image = image; + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java new file mode 100644 index 000000000..0c5d34699 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java @@ -0,0 +1,15 @@ +package org.argeo.eclipse.ui.specific; + +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; + +public class CmsFileDialog extends FileDialog { + public CmsFileDialog(Shell parent, int style) { + super(parent, style); + } + + public CmsFileDialog(Shell parent) { + super(parent); + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java new file mode 100644 index 000000000..638859a85 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java @@ -0,0 +1,32 @@ +package org.argeo.eclipse.ui.specific; + +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Composite; + +public class CmsFileUpload extends FileUpload { + public CmsFileUpload(Composite parent, int style) { + super(parent, style); + } + + @Override + public void setText(String text) { + super.setText(text); + } + + @Override + public String getFileName() { + return super.getFileName(); + } + + @Override + public String[] getFileNames() { + return super.getFileNames(); + } + + @Override + public void addSelectionListener(SelectionListener listener) { + super.addSelectionListener(listener); + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java new file mode 100644 index 000000000..fbb4fbf83 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java @@ -0,0 +1,14 @@ +package org.argeo.eclipse.ui.specific; + +/** RCP specific {@link NLS} to be extended */ +public class DefaultNLS {// extends NLS { +// public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin"; +// +// public DefaultNLS() { +// this(DEFAULT_BUNDLE_LOCATION); +// } +// +// public DefaultNLS(String bundleName) { +// NLS.initializeMessages(bundleName, getClass()); +// } +} \ No newline at end of file diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java new file mode 100644 index 000000000..ac862d794 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java @@ -0,0 +1,7 @@ +package org.argeo.eclipse.ui.specific; + +/** Constants which are specific to RWT.*/ +public interface EclipseUiConstants { + final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName"; + final static String MARKUP_SUPPORT = null; +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java new file mode 100644 index 000000000..d1acbcfc0 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java @@ -0,0 +1,40 @@ +package org.argeo.eclipse.ui.specific; + +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.widgets.Widget; + +/** Static utilities to bridge differences between RCP and RAP */ +public class EclipseUiSpecificUtils { + private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName"; + + public static void setStyleData(Widget widget, Object data) { + widget.setData(CSS_CLASS, data); + } + + public static Object getStyleData(Widget widget) { + return widget.getData(CSS_CLASS); + } + + public static void setMarkupData(Widget widget) { + // does nothing + } + + public static void setMarkupValidationDisabledData(Widget widget) { + // does nothing + } + + /** + * TootlTip support is supported for {@link ColumnViewer} in RCP + * + * @see ColumnViewerToolTipSupport#enableFor(Viewer) + */ + public static void enableToolTipSupport(Viewer viewer) { + if (viewer instanceof ColumnViewer) + ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer); + } + + private EclipseUiSpecificUtils() { + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java new file mode 100644 index 000000000..524447ed0 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java @@ -0,0 +1,48 @@ +package org.argeo.eclipse.ui.specific; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetAdapter; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.widgets.Control; + +public class FileDropAdapter { + + public void prepareDropTarget(Control control, DropTarget dropTarget) { + dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() }); + dropTarget.addDropListener(new DropTargetAdapter() { + @Override + public void dropAccept(DropTargetEvent event) { + if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) { + event.detail = DND.DROP_NONE; + } + } + + @Override + public void drop(DropTargetEvent event) { + handleFileDrop(control, event); + } + }); + } + + public void handleFileDrop(Control control, DropTargetEvent event) { + String fileList[] = null; + FileTransfer ft = FileTransfer.getInstance(); + if (ft.isSupportedType(event.currentDataType)) { + fileList = (String[]) event.data; + } + System.out.println(Arrays.toString(fileList)); + } + + /** Executed in UI thread */ + protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { + + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java new file mode 100644 index 000000000..20163cffa --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java @@ -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 getData(String key) { + Display display = getDisplay(); + if (display == null) + return null; + return (T) display.getData(key); + } + + public static void setData(String key, Object value) { + Display display = getDisplay(); + if (display == null) + throw new IllegalStateException("Not display available"); + display.setData(key, value); + } + + private static Display getDisplay() { + return Display.getCurrent(); + } + + private UiContext() { + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java new file mode 100644 index 000000000..fbb36ddd4 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java @@ -0,0 +1,9 @@ +package org.eclipse.rap.fileupload; + +public interface FileDetails { + String getContentType(); + + long getContentLength(); + + String getFileName(); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java new file mode 100644 index 000000000..a7452806a --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java @@ -0,0 +1,21 @@ +package org.eclipse.rap.fileupload; + +import java.util.EventObject; + +public abstract class FileUploadEvent extends EventObject { + + private static final long serialVersionUID = 1L; + + protected FileUploadEvent(FileUploadHandler source) { + super(source); + } + + public abstract FileDetails[] getFileDetails(); + + public abstract long getContentLength(); + + public abstract long getBytesRead(); + + public abstract Exception getException(); + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java new file mode 100644 index 000000000..7d89300f3 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java @@ -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 getUploadUrl(). Upload listeners can be attached + * to react on progress. When the upload has finished, a FileUploadHandler has + * to be disposed of by calling its dispose() method. + * + * @noextend This class is not intended to be subclassed by clients. + */ +public class FileUploadHandler { + + public FileUploadHandler(FileUploadReceiver fileUploadReceiver) { + } + + public void dispose() { + + } + + public void addUploadListener(FileUploadListener listener) { + + } + + public String getUploadUrl() { + return null; + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java new file mode 100644 index 000000000..b59fd39ea --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java @@ -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. + *

    + * Note: This listener will be called from a different thread than the UI thread. + * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI. + *

    + * + * @see FileUploadEvent + */ +public interface FileUploadListener { + + /** + * Called when new information about an in-progress upload is available. + * + * @param event event object that contains information about the uploaded file + * @see FileUploadEvent#getBytesRead() + */ + void uploadProgress( FileUploadEvent event ); + + /** + * Called when a file upload has finished successfully. + * + * @param event event object that contains information about the uploaded file + * @see FileUploadEvent + */ + void uploadFinished( FileUploadEvent event ); + + /** + * Called when a file upload failed. + * + * @param event event object that contains information about the uploaded file + * @see FileUploadEvent#getErrorMessage() + */ + void uploadFailed( FileUploadEvent event ); + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java new file mode 100644 index 000000000..3f4cf47c4 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2011, 2013 EclipseSource and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * EclipseSource - initial API and implementation + ******************************************************************************/ +package org.eclipse.rap.fileupload; + +import java.io.IOException; +import java.io.InputStream; + + +/** + * Instances of this interface are responsible for reading and processing the data from a file + * upload. + */ +public abstract class FileUploadReceiver { + + /** + * Reads and processes all data from the provided input stream. + * + * @param stream the stream to read from + * @param details the details of the uploaded file like file name, content-type and size + * @throws IOException if an input / output error occurs + */ + public abstract void receive( InputStream stream, FileDetails details ) throws IOException; + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java new file mode 100644 index 000000000..1688594bb --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java @@ -0,0 +1,45 @@ +package org.eclipse.rap.rwt; + +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient; +import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager; +import org.eclipse.rap.rwt.client.Client; +import org.eclipse.rap.rwt.service.ResourceManager; + +public class RWT { + public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT"; + public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED"; + public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED"; + public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT"; + public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS"; + public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS"; + public final static String DEFAULT_THEME_ID = "argeo-rcp:DEFAULT_THEME_ID"; + + public final static int HYPERLINK = 0; + + private static Locale locale = Locale.getDefault(); + private static RcpClient client = new RcpClient(); + private static ResourceManager resourceManager = new RcpResourceManager(); + static { + + } + + public static Locale getLocale() { + return locale; + } + + public static HttpServletRequest getRequest() { + return null; + } + + public static ResourceManager getResourceManager() { + return resourceManager; + } + + public static Client getClient() { + return client; + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java new file mode 100644 index 000000000..6e30aa635 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java @@ -0,0 +1,7 @@ +package org.eclipse.rap.rwt; + +public class SingletonUtil { + public static T getSessionInstance(Class clss) { + return null; + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java new file mode 100644 index 000000000..980a81854 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java @@ -0,0 +1,43 @@ +package org.eclipse.rap.rwt.application; + +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +public abstract class AbstractEntryPoint implements EntryPoint { + private Display display; + private Shell shell; + + protected Shell createShell(Display display) { + return new Shell(display); + } + + protected void createContents(Composite parent) { + + } + + public int createUI() { + display = new Display(); + shell = createShell(display); + shell.setLayout(new GridLayout(1, false)); + createContents(shell); + if (shell.getMaximized()) { + shell.layout(); + } else { + shell.pack(); + } + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + display.dispose(); + return 0; + } + + protected Shell getShell() { + return shell; + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java new file mode 100644 index 000000000..6cb5f29d2 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java @@ -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 properties); + + void addEntryPoint(String path, Class entryPoint, + Map properties); + + void addStyleSheet(String themeId, String styleSheetLocation, + ResourceLoader resourceLoader); + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java new file mode 100644 index 000000000..961ad70f6 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java @@ -0,0 +1,5 @@ +package org.eclipse.rap.rwt.application; + +public interface ApplicationConfiguration { + void configure(Application application); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java new file mode 100644 index 000000000..c0d559a2b --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java @@ -0,0 +1,5 @@ +package org.eclipse.rap.rwt.application; + +public interface EntryPoint { + int createUI(); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java new file mode 100644 index 000000000..d5b24d8fe --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java @@ -0,0 +1,5 @@ +package org.eclipse.rap.rwt.application; + +public interface EntryPointFactory { + public EntryPoint create(); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java new file mode 100644 index 000000000..13daf2195 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java @@ -0,0 +1,5 @@ +package org.eclipse.rap.rwt.application; + +public interface ExceptionHandler { + public void handleException(Throwable throwable); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java new file mode 100644 index 000000000..934feaea6 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java @@ -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 null + * @see ClientService + */ + T getService( Class type ); + +} \ No newline at end of file diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java new file mode 100644 index 000000000..1f19bdd7c --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java @@ -0,0 +1,10 @@ +package org.eclipse.rap.rwt.client; + +public interface WebClient { + public final static String FAVICON = "rcp:FAVICON"; + public final static String PAGE_TITLE = "rcp:PAGE_TITLE"; + public final static String BODY_HTML = "rcp:BODY_HTML"; + public final static String THEME_ID = "rcp:THEME_ID"; + public final static String HEAD_HTML = "rcp:HEAD_HTML"; + public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW"; +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java new file mode 100644 index 000000000..ffba4e43e --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java @@ -0,0 +1,7 @@ +package org.eclipse.rap.rwt.client.service; + +public interface BrowserNavigation extends ClientService { + void pushState(String state, String title); + + void addBrowserNavigationListener(BrowserNavigationListener listener); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java new file mode 100644 index 000000000..3e1b3eb72 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java @@ -0,0 +1,10 @@ +package org.eclipse.rap.rwt.client.service; + +public class BrowserNavigationEvent { + private String state; + + public String getState() { + return state; + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java new file mode 100644 index 000000000..8319c03f7 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java @@ -0,0 +1,5 @@ +package org.eclipse.rap.rwt.client.service; + +public interface BrowserNavigationListener { + public void navigated(BrowserNavigationEvent event); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java new file mode 100644 index 000000000..9f479d139 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java @@ -0,0 +1,6 @@ +package org.eclipse.rap.rwt.client.service; + +import java.io.Serializable; + +public interface ClientService extends Serializable { +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java new file mode 100644 index 000000000..6c44c729c --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java @@ -0,0 +1,5 @@ +package org.eclipse.rap.rwt.client.service; + +public interface JavaScriptExecutor extends ClientService { + public void execute( String code ); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java new file mode 100644 index 000000000..9dae811c7 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012 EclipseSource and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * EclipseSource - initial API and implementation + ******************************************************************************/ +package org.eclipse.rap.rwt.client.service; + +/** + * The UrlLauncher service allows loading an URL in an external window, application or save dialog. + * + * @since 2.0 + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface UrlLauncher extends ClientService { + + /** + * Opens the given URL. + * + * Any HTTP URL or relative URL will be opened in a new window. + * Modern browser may block any attempt to open new windows, but will usually prompt the user to + * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only + * to future attempts. It could also trigger a document reload, causing a session restart. + * + * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client + * to have a matching protocol handler registered. + * + * @param url the URL to open + */ + void openURL( String url ); + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java new file mode 100644 index 000000000..7e7116cf3 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java @@ -0,0 +1,9 @@ +package org.eclipse.rap.rwt.service; + +import java.io.IOException; +import java.io.InputStream; + +public interface ResourceLoader { + public InputStream getResourceAsStream(String resourceName) + throws IOException; +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java new file mode 100644 index 000000000..c3379ea66 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java @@ -0,0 +1,15 @@ +package org.eclipse.rap.rwt.service; + +import java.io.InputStream; + +public interface ResourceManager { + public void register(String name, InputStream in); + + boolean unregister(String name); + + public InputStream getRegisteredContent(String name); + + public String getLocation(String name); + + public boolean isRegistered(String name); +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java new file mode 100644 index 000000000..bed194f31 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java @@ -0,0 +1,12 @@ +package org.eclipse.rap.rwt.service; + +/** Mock, does nothing as this is irrelevant for RCP. */ +public class ServerPushSession { + public void start() { + + } + + public void stop() { + + } +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java new file mode 100644 index 000000000..b2a2005e7 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java @@ -0,0 +1,33 @@ +package org.eclipse.rap.rwt.widgets; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Widget; + +public class DropDown { + private boolean visible=false; + + public DropDown(Widget parent, int style) { + // FIXME implement a shell + } + + public DropDown(Widget parent) { + this(parent, SWT.NONE); + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public boolean isVisible() { + return visible; + } + + public void setItems( String[] items ) { + + } + + public void setSelectionIndex( int selection ) { + + } + +} diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java new file mode 100644 index 000000000..cbf1449e0 --- /dev/null +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java @@ -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; + } + +}