From: Mathieu Date: Tue, 6 Dec 2022 05:11:34 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/unstable' into merge-to-testing X-Git-Tag: v2.1.107~2^2~1 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=55870eba50d8b28e72a3102fd18a17a6f23f7bad;hp=138e686fbf65683c3c94a52f1cfbaf8e02362e19;p=lgpl%2Fargeo-commons.git Merge remote-tracking branch 'origin/unstable' into merge-to-testing --- diff --git a/Makefile b/Makefile index 5d0ce97ce..330bc4fca 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ include sdk.mk -.PHONY: clean all osgi jni +.PHONY: clean all osgi all: osgi $(MAKE) -f Makefile-rcp.mk all @@ -8,8 +8,8 @@ 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 \ @@ -17,21 +17,19 @@ org.argeo.cms \ org.argeo.cms.ux \ org.argeo.cms.ee \ org.argeo.cms.lib.jetty \ -org.argeo.cms.lib.equinox \ org.argeo.cms.lib.sshd \ -org.argeo.cms.lib.pgsql \ 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.swt.rap.cli \ swt/rap/org.argeo.cms.e4.rap \ DEP_CATEGORIES = \ org.argeo.tp \ -org.argeo.tp.apache \ +org.argeo.tp.crypto \ org.argeo.tp.jetty \ osgi/api/org.argeo.tp.osgi \ osgi/equinox/org.argeo.tp.eclipse \ @@ -46,28 +44,10 @@ org.argeo.api.uuid \ org.argeo.api.acr \ org.argeo.api.cms -jni: - $(MAKE) -C jni - clean: rm -rf $(BUILD_BASE) - $(MAKE) -C jni clean $(MAKE) -f Makefile-rcp.mk clean A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES))) -native-image: - mkdir -p $(A2_OUTPUT)/libexec/$(A2_CATEGORY) -# cd $(A2_OUTPUT)/libexec/$(A2_CATEGORY) && /opt/graalvm-ce/bin/native-image \ - -cp $(A2_CLASSPATH):$(A2_BUNDLES_CLASSPATH) org.argeo.eclipse.ui.jetty.CmsRapCli \ - --enable-url-protocols=http,https \ - -H:AdditionalSecurityProviders=sun.security.jgss.SunProvider,org.bouncycastle.jce.provider.BouncyCastleProvider,net.i2p.crypto.eddsa.EdDSASecurityProvider \ - --initialize-at-build-time=org.argeo.init.logging.ThinLogging,org.slf4j.LoggerFactory \ - --no-fallback - cd $(A2_OUTPUT)/libexec/$(A2_CATEGORY) && /opt/graalvm-ce/bin/native-image \ - -cp $(A2_CLASSPATH):$(A2_BUNDLES_CLASSPATH) org.argeo.cms.ux.cli.FileSync \ - --initialize-at-build-time=org.argeo.init.logging.ThinLogging,org.slf4j.LoggerFactory \ - --no-fallback - - 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 7f1ed9ee2..29f1a23a3 100644 --- a/Makefile-rcp.mk +++ b/Makefile-rcp.mk @@ -3,35 +3,24 @@ include sdk.mk all: osgi -move-rcp: osgi - mkdir -p $(A2_OUTPUT)/swt/rcp/$(A2_CATEGORY) - mv -v $(A2_OUTPUT)/$(A2_CATEGORY)/*.rcp.$(MAJOR).$(MINOR).jar $(A2_OUTPUT)/swt/rcp/$(A2_CATEGORY) - mv -v $(A2_OUTPUT)/$(A2_CATEGORY)/*.rcp.cli.$(MAJOR).$(MINOR).jar $(A2_OUTPUT)/swt/rcp/$(A2_CATEGORY) - #touch $(BUILD_BASE)/*.rcp/bnd.bnd - A2_CATEGORY = org.argeo.cms BUNDLES = \ swt/rcp/org.argeo.swt.specific.rcp \ swt/rcp/org.argeo.cms.swt.rcp \ -swt/rcp/org.argeo.cms.swt.rcp.cli \ swt/rcp/org.argeo.cms.e4.rcp \ -A2_OUTPUT = $(SDK_BUILD_BASE)/a2 -A2_BASE = $(A2_OUTPUT) - DEP_CATEGORIES = \ org.argeo.cms \ swt/org.argeo.cms \ org.argeo.tp \ -org.argeo.tp.apache \ +org.argeo.tp.crypto \ org.argeo.tp.jetty \ 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 \ -org.argeo.tp.jcr clean: 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/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/Content.java b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java index b605fa1e0..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 @@ -128,6 +128,14 @@ public interface Content extends Iterable, Map { 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(); @@ -138,8 +146,12 @@ public interface Content extends Iterable, Map { return false; } - default boolean hasContentClass(QNamed contentClass) { - return hasContentClass(contentClass.qName()); + /** 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()])); } /* 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 55ad079ec..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 @@ -28,7 +28,7 @@ public enum CrAttributeType { // we do not support short and float, like recent additions to Java // (e.g. optional primitives) DATE_TIME(Instant.class, W3C_XML_SCHEMA_NS_URI, "dateTime", new InstantFormatter()), // - UUID(UUID.class, CrName.CR_NAMESPACE_URI, "uuid", new UuidFormatter()), // + 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()), // ; 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 f61833507..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 @@ -34,14 +34,7 @@ public enum CrName implements QNamed { // ; - 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"; + // private final ContentName value; @@ -55,12 +48,12 @@ public enum CrName implements QNamed { @Override public String getNamespace() { - return CR_NAMESPACE_URI; + 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/RuntimeNamespaceContext.java b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java index 0941597d7..1c55156ee 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java @@ -61,9 +61,9 @@ public class RuntimeNamespaceContext implements NamespaceContext { register(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XSD_INSTANCE_DEFAULT_PREFIX); // Argeo specific - register(CrName.CR_NAMESPACE_URI, CrName.CR_DEFAULT_PREFIX); - register(CrName.LDAP_NAMESPACE_URI, CrName.LDAP_DEFAULT_PREFIX); - register(CrName.ROLE_NAMESPACE_URI, CrName.ROLE_DEFAULT_PREFIX); + 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() { 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/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/ProvidedRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java index 7134007b9..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 @@ -7,7 +7,7 @@ import org.argeo.api.acr.ContentRepository; /** A {@link ContentRepository} implementation. */ public interface ProvidedRepository extends ContentRepository { - void registerTypes(String prefix, String namespaceURI, String schemaSystemId); + void registerTypes(ContentNamespace... namespaces); ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types); 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 9c4f6e633..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 @@ -22,7 +22,7 @@ public interface ProvidedSession extends ContentSession { UUID getUuid(); - Content getSessionRunDir(); +// Content getSessionRunDir(); /* * NAMESPACE CONTEXT 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/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java index b2a12a603..935247fee 100644 --- a/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java +++ b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java @@ -1,5 +1,6 @@ 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; 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 index b82308a2b..5bbfcfa07 100644 --- a/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java +++ b/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java @@ -18,8 +18,6 @@ import org.apache.commons.cli.ParseException; /** Base class for a CLI managing sub commands. */ public abstract class CommandsCli implements DescribedCommand { - public final static String HELP = "help"; - private final String commandName; private Map, ?>> commands = new TreeMap<>(); @@ -33,11 +31,17 @@ public abstract class CommandsCli implements DescribedCommand { 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 { @@ -50,6 +54,18 @@ public abstract class CommandsCli implements DescribedCommand { } 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 { @@ -85,7 +101,7 @@ public abstract class CommandsCli implements DescribedCommand { protected void addCommandsCli(CommandsCli commandsCli) { addCommand(commandsCli.getCommandName(), commandsCli); - commandsCli.addCommand(HELP, new HelpCommand(this, commandsCli)); + commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli)); } public String getCommandName() { @@ -101,7 +117,7 @@ public abstract class CommandsCli implements DescribedCommand { } public HelpCommand getHelpCommand() { - return (HelpCommand) getCommand(HELP); + return (HelpCommand) getCommand(HelpCommand.HELP); } public Function, String> getDefaultCommand() { @@ -111,10 +127,15 @@ public abstract class CommandsCli implements DescribedCommand { /** In order to implement quickly a main method. */ public static void mainImpl(CommandsCli cli, String[] args) { try { - cli.addCommand(CommandsCli.HELP, new HelpCommand(null, cli)); + cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli)); Object output = cli.apply(Arrays.asList(args)); - System.out.println(output); + 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(); 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 index 7a9d5d901..51cb2ceca 100644 --- a/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java +++ b/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java @@ -41,6 +41,11 @@ public interface DescribedCommand extends Function, T> { 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); 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 index d5285a60c..cd51d7695 100644 --- a/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java +++ b/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java @@ -6,10 +6,20 @@ 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; @@ -91,6 +101,9 @@ public class HelpCommand implements DescribedCommand { 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(); } } @@ -99,17 +112,25 @@ public class HelpCommand implements DescribedCommand { String usage = "java " + command.getClass().getName() + (command.getUsage() != null ? " " + command.getUsage() : ""); HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(), - helpLeftPad, helpDescPad, command.getExamples(), false); + 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(); - formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(), - helpLeftPad, helpDescPad, command.getExamples(), false); + Options options = command.getOptions(); + options.addOption(HelpCommand.HELP_OPTION); + formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad, + helpDescPad, command.getExamples(), false); } 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.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/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.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/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java index 6dbd403dd..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 @@ -143,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; } 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.cms.cli/bnd.bnd b/org.argeo.cms.cli/bnd.bnd index e69de29bb..07d35f219 100644 --- a/org.argeo.cms.cli/bnd.bnd +++ 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/src/org/argeo/cms/cli/ArgeoCli.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java index 1dd57f3df..b55f9d6ad 100644 --- a/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java +++ b/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java @@ -15,6 +15,7 @@ public class ArgeoCli extends CommandsCli { 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")); @@ -22,7 +23,11 @@ public class ArgeoCli extends CommandsCli { @Override public String getDescription() { - return "Argeo utilities"; + 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/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.ee/OSGI-INF/statusHandler.xml b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml index 37e6c5fba..b6e9bfd05 100644 --- a/org.argeo.cms.ee/OSGI-INF/statusHandler.xml +++ b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml @@ -4,6 +4,6 @@ - + diff --git a/org.argeo.cms.ee/bnd.bnd b/org.argeo.cms.ee/bnd.bnd index 08ce1365c..f09995c00 100644 --- a/org.argeo.cms.ee/bnd.bnd +++ b/org.argeo.cms.ee/bnd.bnd @@ -1,5 +1,4 @@ Import-Package:\ -org.argeo.util.http,\ 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,\ 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 index 205699464..672722946 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java @@ -6,7 +6,7 @@ import java.io.Writer; import javax.servlet.http.HttpServletResponse; import org.argeo.api.cms.CmsLog; -import org.argeo.util.ExceptionsChain; +import org.argeo.cms.util.ExceptionsChain; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; 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 index 0628eae36..d18637d3f 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java @@ -15,7 +15,7 @@ import javax.servlet.http.HttpServletResponse; import org.argeo.api.cms.CmsAuth; import org.argeo.api.cms.CmsSessionId; -import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.CurrentUser; import org.argeo.cms.auth.RemoteAuthCallback; import org.argeo.cms.auth.RemoteAuthCallbackHandler; import org.argeo.cms.servlet.ServletHttpRequest; 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 index 983202ad2..c355ecd8d 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java @@ -15,13 +15,13 @@ 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.cms.CmsUserManager; +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.argeo.util.naming.NamingUtils; import org.osgi.service.useradmin.Authorization; import com.fasterxml.jackson.core.JsonGenerator; 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 index 6a5208730..d3c0eb540 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java @@ -2,11 +2,8 @@ package org.argeo.cms.servlet; import java.io.IOException; import java.net.URL; -import java.net.http.HttpHeaders; -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; @@ -19,7 +16,6 @@ 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.argeo.util.http.HttpHeader; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import org.osgi.service.http.context.ServletContextHelper; 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 index de47365ca..0c600e54b 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java @@ -15,8 +15,13 @@ public class ServletHttpResponse implements RemoteAuthResponse { } @Override - public void setHeader(String keys, String value) { - response.setHeader(keys, value); + 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/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 index b2f739449..63d59a88d 100644 --- 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 @@ -15,6 +15,10 @@ 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; 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 index 5a29fbe84..f5e9c0394 100644 --- 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 @@ -18,7 +18,8 @@ import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpPrincipal; import com.sun.net.httpserver.HttpsExchange; -public class ServletHttpExchange extends HttpsExchange { +/** Integrates {@link HttpsExchange} in a servlet container. */ +class ServletHttpExchange extends HttpsExchange { private final HttpContext httpContext; private final HttpServletRequest httpServletRequest; private final HttpServletResponse httpServletResponse; 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 index c762b67ec..2b2ffcb10 100644 --- 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 @@ -12,9 +12,9 @@ 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.FilterRequirement; import org.argeo.cms.osgi.PublishNamespace; -import org.argeo.osgi.util.FilterRequirement; +import org.argeo.cms.util.StreamUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; @@ -126,7 +126,7 @@ public class PkgServlet extends HttpServlet { } try (InputStream in = internalURL.openStream()) { - IOUtils.copy(in, resp.getOutputStream()); + StreamUtils.copy(in, resp.getOutputStream()); } } 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 index c71c862d6..defc59efc 100644 --- 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 @@ -1,9 +1,11 @@ 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; @@ -12,11 +14,13 @@ 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 = "/event/{topic}", configurator = CmsWebSocketConfigurator.class) +@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; @@ -47,4 +51,13 @@ public class EventEndpoint implements CmsEventSubscriber { 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 index b81cc591d..dcbce67b1 100644 --- 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 @@ -1,16 +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 = "/ping", configurator = PublicWebSocketConfigurator.class) +@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/TestEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java index 0575726d3..f0c7fca3a 100644 --- 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 @@ -16,9 +16,9 @@ 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.argeo.util.naming.NamingUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; @@ -31,7 +31,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; /** Provides WebSocket access. */ -@ServerEndpoint(value = "/test/{topic}", configurator = CmsWebSocketConfigurator.class) +@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class) public class TestEndpoint implements EventHandler { private final static CmsLog log = CmsLog.getLog(TestEndpoint.class); 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 index 3a978c8ae..b003c6372 100644 --- 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 @@ -1,6 +1,8 @@ package org.argeo.cms.websocket.server; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import javax.websocket.HandshakeResponse; @@ -14,9 +16,14 @@ public class WebSocketHandshakeResponse implements RemoteAuthResponse { } @Override - public void setHeader(String key, String value) { - handshakeResponse.getHeaders().put(key, Collections.singletonList(value)); + 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.lib.equinox/.classpath b/org.argeo.cms.lib.equinox/.classpath deleted file mode 100644 index 81fe078c2..000000000 --- a/org.argeo.cms.lib.equinox/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.lib.equinox/.gitignore b/org.argeo.cms.lib.equinox/.gitignore deleted file mode 100644 index 09e3bc9b2..000000000 --- a/org.argeo.cms.lib.equinox/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/bin/ -/target/ diff --git a/org.argeo.cms.lib.equinox/.project b/org.argeo.cms.lib.equinox/.project deleted file mode 100644 index c551d5dff..000000000 --- a/org.argeo.cms.lib.equinox/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - 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/org.argeo.cms.lib.equinox/META-INF/.gitignore b/org.argeo.cms.lib.equinox/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/org.argeo.cms.lib.equinox/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml b/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml deleted file mode 100644 index 6a1336220..000000000 --- a/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.lib.equinox/bnd.bnd b/org.argeo.cms.lib.equinox/bnd.bnd deleted file mode 100644 index 2c83158e2..000000000 --- a/org.argeo.cms.lib.equinox/bnd.bnd +++ /dev/null @@ -1,2 +0,0 @@ -Service-Component: \ -OSGI-INF/jettyServiceFactory.xml,\ diff --git a/org.argeo.cms.lib.equinox/build.properties b/org.argeo.cms.lib.equinox/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/org.argeo.cms.lib.equinox/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java deleted file mode 100644 index 1b1b42961..000000000 --- a/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java +++ /dev/null @@ -1,152 +0,0 @@ -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; - -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$ - - // holder.setInitParameter(JettyConstants.CONTEXT_PATH, - // httpContext.getContextPath()); - 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/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java deleted file mode 100644 index 50be8b7a7..000000000 --- a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java +++ /dev/null @@ -1,231 +0,0 @@ -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.websocket.server.CmsWebSocketConfigurator; -import org.argeo.cms.websocket.server.TestEndpoint; -import org.argeo.util.LangUtils; -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/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java b/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java deleted file mode 100644 index 8ceb358dd..000000000 --- a/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java +++ /dev/null @@ -1,25 +0,0 @@ -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/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java deleted file mode 100644 index 7be23fc0f..000000000 --- a/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java +++ /dev/null @@ -1,65 +0,0 @@ -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/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java b/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java deleted file mode 100644 index 41c8ce9b0..000000000 --- a/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Equinox Jetty extensions. */ -package org.argeo.equinox.jetty; \ No newline at end of file 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 index 3d4a57b9e..b0b348d9c 100644 --- 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 @@ -3,30 +3,26 @@ 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 static final CmsLog log = CmsLog.getLog(CmsJettyServer.class); - -// private Server server; -// private Path tempDir; -// -// private ServerConnector httpConnector; -// private ServerConnector httpsConnector; private Path tempDir; - // WebSocket -// private ServerContainer wsServerContainer; -// private ServerEndpointConfig.Configurator wsEndpointConfigurator; - -// private Authenticator defaultAuthenticator; + private CompletableFuture serverContainer = new CompletableFuture<>(); protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { } @@ -41,6 +37,7 @@ public class CmsJettyServer extends JettyHttpServer { super.start(); } + @Override protected ServletContextHandler createRootContextHandler() { ServletContextHandler servletContextHandler = new ServletContextHandler(); servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER, @@ -53,51 +50,30 @@ public class CmsJettyServer extends JettyHttpServer { 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); -// enableWebSocket(servletContextHandler); - } -// @Override -// public synchronized HttpContext createContext(String path) { -// HttpContext httpContext = super.createContext(path); -// httpContext.setAuthenticator(defaultAuthenticator); -// return httpContext; -// } - -// protected void enableWebSocket(ServletContextHandler servletContextHandler) { -// String webSocketEnabled = getDeployProperty(CmsDeployProperty.WEBSOCKET_ENABLED); -// // web socket -// if (webSocketEnabled != null && webSocketEnabled.equals(Boolean.toString(true))) { -//// JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() { -//// -//// @Override -//// public void accept(ServletContext servletContext, ServerContainer serverContainer) -//// throws DeploymentException { -////// wsServerContainer = serverContainer; -//// -//// CmsWebSocketConfigurator wsEndpointConfigurator = new CmsWebSocketConfigurator(); -//// -//// ServerEndpointConfig config = ServerEndpointConfig.Builder -//// .create(TestEndpoint.class, "/ws/test/events/{topic}").configurator(wsEndpointConfigurator) -//// .build(); -//// try { -//// serverContainer.addEndpoint(config); -//// } catch (DeploymentException e) { -//// throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e); -//// } -//// } -//// }); -// } -// } - -// public void setDefaultAuthenticator(Authenticator defaultAuthenticator) { -// this.defaultAuthenticator = defaultAuthenticator; -// } + /* + * 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 index 5876d52e8..551e54e05 100644 --- 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 @@ -1,23 +1,15 @@ package org.argeo.cms.jetty; -import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; -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.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; @@ -27,12 +19,13 @@ import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -/** Trivial implementation of @{HttpContext}. */ -class JettyHttpContext extends HttpContext { +/** + * 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 ServletContextHandler contextHandler; - private final ContextAttributes attributes; private final List filters = new ArrayList<>(); private HttpHandler handler; @@ -40,22 +33,13 @@ class JettyHttpContext extends HttpContext { public JettyHttpContext(JettyHttpServer httpServer, String path) { this.httpServer = httpServer; + if (!path.endsWith("/")) + throw new IllegalArgumentException("Path " + path + " should end with a /"); this.path = path; - - // Jetty context handler - ServletContextHandler 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); - contextHandler = servletContextHandler; - - attributes = new ContextAttributes(); } + protected abstract ServletContextHandler getServletContextHandler(); + @Override public HttpHandler getHandler() { return handler; @@ -67,32 +51,6 @@ class JettyHttpContext extends HttpContext { throw new IllegalArgumentException("Handler is already set"); Objects.requireNonNull(handler); this.handler = handler; - - // web socket - if (handler instanceof WebsocketEndpoints) { - JavaxWebSocketServletContainerInitializer.configure(contextHandler, new Configurator() { - - @Override - public void accept(ServletContext servletContext, ServerContainer serverContainer) - throws DeploymentException { -// CmsWebSocketConfigurator wsEndpointConfigurator = new CmsWebSocketConfigurator(); - - for (Class clss : ((WebsocketEndpoints) handler).getEndPoints()) { -// Class clss = websocketEndpoints.get(path); -// ServerEndpointConfig config = ServerEndpointConfig.Builder.create(clss, path) -// .configurator(wsEndpointConfigurator).build(); - serverContainer.addEndpoint(clss); - } - } - }); - } - - if (httpServer.isStarted()) - try { - contextHandler.start(); - } catch (Exception e) { - throw new IllegalStateException("Cannot start context handler", e); - } } @Override @@ -102,12 +60,11 @@ class JettyHttpContext extends HttpContext { @Override public HttpServer getServer() { - return httpServer; + return getJettyHttpServer(); } - @Override - public Map getAttributes() { - return attributes; + protected JettyHttpServer getJettyHttpServer() { + return httpServer; } @Override @@ -127,51 +84,4 @@ class JettyHttpContext extends HttpContext { return authenticator; } - ServletContextHandler getContextHandler() { - return contextHandler; - } - - private class ContextAttributes extends AbstractMap { - @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/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java index 80cff8b01..363bbaebe 100644 --- 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 @@ -8,11 +8,12 @@ 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.util.http.HttpServerUtils; +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; @@ -32,6 +33,7 @@ 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); @@ -51,6 +53,7 @@ public class JettyHttpServer extends HttpsServer { private final Map contexts = new TreeMap<>(); + private ServletContextHandler rootContextHandler; protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); private boolean started; @@ -91,19 +94,13 @@ public class JettyHttpServer extends HttpsServer { // holder // context - ServletContextHandler rootContextHandler = createRootContextHandler(); + rootContextHandler = createRootContextHandler(); // httpContext.addServlet(holder, "/*"); if (rootContextHandler != null) configureRootContextHandler(rootContextHandler); -// server.setHandler(rootContextHandler); -// ContextHandlerCollection contextHandlers = new ContextHandlerCollection(); if (rootContextHandler != null && !contexts.containsKey("/")) contextHandlerCollection.addHandler(rootContextHandler); -// for (String contextPath : contexts.keySet()) { -// JettyHttpContext ctx = contexts.get(contextPath); -// contextHandlers.addHandler(ctx.getContextHandler()); -// } server.setHandler(contextHandlerCollection); @@ -115,20 +112,93 @@ public class JettyHttpServer extends HttpsServer { // Addresses String httpHost = getDeployProperty(CmsDeployProperty.HOST); String fallBackHostname = cmsState != null ? cmsState.getHostname() : "::1"; - if (httpConnector != null) + if (httpConnector != null) { httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname, httpConnector.getLocalPort()); - if (httpsConnector != null) + } 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) { - throw new IllegalStateException("Cannot start Jetty HTTPS server", 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); + } } } @@ -141,12 +211,11 @@ public class JettyHttpServer extends HttpsServer { public void stop() { try { - // serverConnector.close(); server.stop(); // TODO delete temp dir started = false; } catch (Exception e) { - e.printStackTrace(); + log.error("Cannot stop Jetty HTTP server", e); } } @@ -172,12 +241,15 @@ public class JettyHttpServer extends HttpsServer { @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 JettyHttpContext(this, path); + + JettyHttpContext httpContext = new ServletHttpContext(this, path); contexts.put(path, httpContext); - contextHandlerCollection.addHandler(httpContext.getContextHandler()); + contextHandlerCollection.addHandler(httpContext.getServletContextHandler()); return httpContext; } @@ -186,8 +258,10 @@ public class JettyHttpServer extends HttpsServer { if (!contexts.containsKey(path)) throw new IllegalArgumentException("Context " + path + " does not exist"); JettyHttpContext httpContext = contexts.remove(path); - // TODO stop handler first? - contextHandlerCollection.removeHandler(httpContext.getContextHandler()); + if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) { + // TODO stop handler first? + contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler()); + } } @Override @@ -197,7 +271,10 @@ public class JettyHttpServer extends HttpsServer { @Override public InetSocketAddress getAddress() { - return httpAddress; + InetSocketAddress res = httpAddress != null ? httpAddress : httpsAddress; + if (res == null) + throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available"); + return res; } @Override @@ -210,80 +287,6 @@ public class JettyHttpServer extends HttpsServer { return httpsConfigurator; } - protected void configureConnectors() { - HttpConfiguration httpConfiguration = new HttpConfiguration(); - - String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT); - String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT); - - /// TODO make it more generic - String httpHost = getDeployProperty(CmsDeployProperty.HOST); -// String httpsHost = getFrameworkProp( -// JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_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; - // props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled); - boolean httpsEnabled = httpsPortStr != null; - // props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled); - if (httpsEnabled) { - int httpsPort = Integer.parseInt(httpsPortStr); - httpConfiguration.setSecureScheme("https"); - httpConfiguration.setSecurePort(httpsPort); - } - - if (httpEnabled) { - 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 https_config = new HttpConfiguration(httpConfiguration); - https_config.addCustomizer(new SecureRequestCustomizer()); - https_config.setUriCompliance(UriCompliance.LEGACY); - - // HTTPS connector - httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), - new HttpConnectionFactory(https_config)); - int httpsPort = Integer.parseInt(httpsPortStr); - httpsConnector.setPort(httpsPort); - httpsConnector.setHost(httpHost); - } - - } - - } - protected String getDeployProperty(CmsDeployProperty deployProperty) { return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty()) : System.getProperty(deployProperty.getProperty()); @@ -291,7 +294,7 @@ public class JettyHttpServer extends HttpsServer { private String httpPortsMsg() { - return (httpConnector != null ? "HTTP " + getHttpPort() + " " : " ") + return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "") + (httpsConnector != null ? "HTTPS " + getHttpsPort() : ""); } @@ -319,10 +322,18 @@ public class JettyHttpServer extends HttpsServer { this.cmsState = cmsState; } - public boolean isStarted() { + 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"); 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.pgsql/.classpath b/org.argeo.cms.lib.pgsql/.classpath deleted file mode 100644 index 81fe078c2..000000000 --- a/org.argeo.cms.lib.pgsql/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.lib.pgsql/.project b/org.argeo.cms.lib.pgsql/.project deleted file mode 100644 index 3cd5f6fb4..000000000 --- a/org.argeo.cms.lib.pgsql/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.lib.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.lib.pgsql/bnd.bnd b/org.argeo.cms.lib.pgsql/bnd.bnd deleted file mode 100644 index 9c7300926..000000000 --- a/org.argeo.cms.lib.pgsql/bnd.bnd +++ /dev/null @@ -1 +0,0 @@ -Import-Package: org.postgresql;version="[42,43)" diff --git a/org.argeo.cms.lib.pgsql/build.properties b/org.argeo.cms.lib.pgsql/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/org.argeo.cms.lib.pgsql/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/org.argeo.cms.lib.pgsql/src/org/argeo/cms/sql/postgres/CheckPg.java b/org.argeo.cms.lib.pgsql/src/org/argeo/cms/sql/postgres/CheckPg.java deleted file mode 100644 index bc002a6a5..000000000 --- a/org.argeo.cms.lib.pgsql/src/org/argeo/cms/sql/postgres/CheckPg.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.cms.sql.postgres; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import org.postgresql.Driver; - -/** Simple PostgreSQL check. */ -public class CheckPg { - - public List 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.lib.sshd/META-INF/native-image/jni-config.json b/org.argeo.cms.lib.sshd/META-INF/native-image/jni-config.json deleted file mode 100644 index 7d14cdbcf..000000000 --- a/org.argeo.cms.lib.sshd/META-INF/native-image/jni-config.json +++ /dev/null @@ -1,75 +0,0 @@ -[ -{ - "name":"java.lang.Boolean", - "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.ClassLoader", - "methods":[ - {"name":"getPlatformClassLoader","parameterTypes":[] }, - {"name":"loadClass","parameterTypes":["java.lang.String"] } - ] -}, -{ - "name":"java.lang.String", - "methods":[ - {"name":"","parameterTypes":["byte[]"] }, - {"name":"getBytes","parameterTypes":[] } - ] -}, -{ - "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" -}, -{ - "name":"org.apache.tomcat.jni.FileInfo", - "fields":[ - {"name":"atime"}, - {"name":"csize"}, - {"name":"ctime"}, - {"name":"device"}, - {"name":"filehand"}, - {"name":"filetype"}, - {"name":"fname"}, - {"name":"group"}, - {"name":"inode"}, - {"name":"mtime"}, - {"name":"name"}, - {"name":"nlink"}, - {"name":"pool"}, - {"name":"protection"}, - {"name":"size"}, - {"name":"user"}, - {"name":"valid"} - ], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.tomcat.jni.Sockaddr", - "fields":[ - {"name":"family"}, - {"name":"hostname"}, - {"name":"next"}, - {"name":"pool"}, - {"name":"port"}, - {"name":"servname"} - ], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", - "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] -}, -{ - "name":"sun.management.VMManagementImpl", - "fields":[ - {"name":"compTimeMonitoringSupport"}, - {"name":"currentThreadCpuTimeSupport"}, - {"name":"objectMonitorUsageSupport"}, - {"name":"otherThreadCpuTimeSupport"}, - {"name":"remoteDiagnosticCommandsSupport"}, - {"name":"synchronizerUsageSupport"}, - {"name":"threadAllocatedMemorySupport"}, - {"name":"threadContentionMonitoringSupport"} - ] -} -] diff --git a/org.argeo.cms.lib.sshd/META-INF/native-image/predefined-classes-config.json b/org.argeo.cms.lib.sshd/META-INF/native-image/predefined-classes-config.json deleted file mode 100644 index 0e79b2c5d..000000000 --- a/org.argeo.cms.lib.sshd/META-INF/native-image/predefined-classes-config.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "type":"agent-extracted", - "classes":[ - ] - } -] - diff --git a/org.argeo.cms.lib.sshd/META-INF/native-image/proxy-config.json b/org.argeo.cms.lib.sshd/META-INF/native-image/proxy-config.json deleted file mode 100644 index f9dde3405..000000000 --- a/org.argeo.cms.lib.sshd/META-INF/native-image/proxy-config.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "interfaces":["org.apache.sshd.common.channel.ChannelListener"]} - , - { - "interfaces":["org.apache.sshd.common.forward.PortForwardingEventListener"]} - , - { - "interfaces":["org.apache.sshd.common.session.SessionListener"]} - -] diff --git a/org.argeo.cms.lib.sshd/META-INF/native-image/reflect-config.json b/org.argeo.cms.lib.sshd/META-INF/native-image/reflect-config.json deleted file mode 100644 index e68e2b52d..000000000 --- a/org.argeo.cms.lib.sshd/META-INF/native-image/reflect-config.json +++ /dev/null @@ -1,503 +0,0 @@ -[ -{ - "name":"java.security.KeyFactory", - "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }] -}, -{ - "name":"java.security.KeyPairGenerator", - "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }] -}, -{ - "name":"java.security.MessageDigest", - "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }] -}, -{ - "name":"java.security.SecureRandomParameters" -}, -{ - "name":"java.security.Signature", - "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }] -}, -{ - "name":"java.security.cert.PKIXRevocationChecker" -}, -{ - "name":"javax.crypto.KeyAgreement", - "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }] -}, -{ - "name":"org.apache.sshd.common.SshConstants", - "allPublicFields":true -}, -{ - "name":"org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.sshd.common.session.SessionListener", - "methods":[ - {"name":"sessionClosed","parameterTypes":["org.apache.sshd.common.session.Session"] }, - {"name":"sessionCreated","parameterTypes":["org.apache.sshd.common.session.Session"] }, - {"name":"sessionEstablished","parameterTypes":["org.apache.sshd.common.session.Session"] }, - {"name":"sessionEvent","parameterTypes":["org.apache.sshd.common.session.Session","org.apache.sshd.common.session.SessionListener$Event"] }, - {"name":"sessionNegotiationEnd","parameterTypes":["org.apache.sshd.common.session.Session","java.util.Map","java.util.Map","java.util.Map","java.lang.Throwable"] }, - {"name":"sessionNegotiationOptionsCreated","parameterTypes":["org.apache.sshd.common.session.Session","java.util.Map"] }, - {"name":"sessionNegotiationStart","parameterTypes":["org.apache.sshd.common.session.Session","java.util.Map","java.util.Map"] }, - {"name":"sessionPeerIdentificationLine","parameterTypes":["org.apache.sshd.common.session.Session","java.lang.String","java.util.List"] }, - {"name":"sessionPeerIdentificationReceived","parameterTypes":["org.apache.sshd.common.session.Session","java.lang.String","java.util.List"] }, - {"name":"sessionPeerIdentificationSend","parameterTypes":["org.apache.sshd.common.session.Session","java.lang.String","java.util.List"] } - ] -}, -{ - "name":"org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.EXTERNAL$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.LMS$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.SPHINCSPlus$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.dh.KeyPairGeneratorSpi", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi$EC", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi$EC", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.ec.SignatureSpi$ecDSA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyAgreementSpi$X25519", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyAgreementSpi$X448", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyFactorySpi$X25519", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyFactorySpi$X448", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$X25519", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$X448", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.rsa.DigestSignatureSpi$SHA512", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Blake3$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.MD5$Digest", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA1$Digest", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA224$Digest", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Digest", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA384$Digest", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA512$Digest", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.bouncycastle.jce.provider.BouncyCastleProvider", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -} -] diff --git a/org.argeo.cms.lib.sshd/META-INF/native-image/resource-config.json b/org.argeo.cms.lib.sshd/META-INF/native-image/resource-config.json deleted file mode 100644 index 7c33b1bf5..000000000 --- a/org.argeo.cms.lib.sshd/META-INF/native-image/resource-config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "resources":{ - "includes":[ - { - "pattern":"\\Qorg/apache/sshd/sshd-version.properties\\E" - }, - { - "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" - } - ]}, - "bundles":[] -} diff --git a/org.argeo.cms.lib.sshd/META-INF/native-image/serialization-config.json b/org.argeo.cms.lib.sshd/META-INF/native-image/serialization-config.json deleted file mode 100644 index bf554e062..000000000 --- a/org.argeo.cms.lib.sshd/META-INF/native-image/serialization-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "types":[ - ], - "lambdaCapturingTypes":[ - ] -} 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 index be41f9cbd..cde6c935a 100644 --- 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 @@ -9,7 +9,7 @@ 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.util.OS; +import org.argeo.cms.util.OS; /** A simple SSH server with some defaults. Supports SCP. */ public class BasicSshServer { 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 index 71b636576..31e63411a 100644 --- 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 @@ -11,7 +11,6 @@ import java.security.KeyPair; import java.util.Map; import java.util.Scanner; -import org.apache.commons.io.IOUtils; import org.apache.sshd.agent.SshAgent; import org.apache.sshd.agent.SshAgentFactory; import org.apache.sshd.agent.local.LocalAgentFactory; @@ -25,6 +24,7 @@ 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); @@ -124,7 +124,7 @@ public class SshSync { log.debug("Relative copied file " + relativeCopiedFile); try (OutputStream out = Files.newOutputStream(copiedFile); InputStream in = Files.newInputStream(testPath)) { - IOUtils.copy(in, out); + StreamUtils.copy(in, out); } log.debug("Copied " + testPath + " to " + copiedFile); Files.delete(testPath); 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 index 4ec456b34..287727544 100644 --- 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 @@ -2,6 +2,7 @@ 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); 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 index eadefa593..852cb5221 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java @@ -12,12 +12,11 @@ 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.apache.commons.io.FilenameUtils; public class SvgToPng { public void convertSvgDir(Path sourceDir, Path targetDir, int width) { - System.out.println("##\n## " + width + "px - " + sourceDir+"\n##"); + System.out.println("##\n## " + width + "px - " + sourceDir + "\n##"); try { if (targetDir == null) targetDir = sourceDir.getParent().resolve(Integer.toString(width)); @@ -30,7 +29,8 @@ public class SvgToPng { transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width); for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) { - String baseName = FilenameUtils.getBaseName(source.toString()); + // FIXME extract base name + String baseName = null; // = FilenameUtils.getBaseName(source.toString()); Path target = targetDir.resolve(baseName + ".png"); convertSvgFile(transcoder, source, target); } 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 index 916cc74f2..087b4ff7c 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java @@ -5,7 +5,7 @@ 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.util.CurrentSubject; +import org.argeo.cms.util.CurrentSubject; public class CmsUxUtils { public static ContentSession getContentSession(ContentRepository contentRepository, CmsView cmsView) { 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 index 43a403415..baaa25238 100644 --- 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 @@ -5,11 +5,10 @@ import java.util.Iterator; import java.util.List; import org.argeo.api.acr.Content; -import org.argeo.api.acr.NamespaceUtils; -import org.argeo.cms.ux.widgets.AbstractDataPart; +import org.argeo.cms.ux.widgets.AbstractHierarchicalPart; import org.argeo.cms.ux.widgets.HierarchicalPart; -public class ContentHierarchicalPart extends AbstractDataPart implements HierarchicalPart { +public class ContentHierarchicalPart extends AbstractHierarchicalPart implements HierarchicalPart { @Override public List getChildren(Content content) { List res = new ArrayList<>(); @@ -27,14 +26,4 @@ public class ContentHierarchicalPart extends AbstractDataPart protected boolean isLeaf(Content content) { return false; } - - @Override - public String getText(Content model) { - try { - return NamespaceUtils.toPrefixedName(model.getName()); - } catch (IllegalStateException e) { - return model.getName().toString(); - } - } - } 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 index 958fcde6a..04811af87 100644 --- 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 @@ -3,12 +3,11 @@ package org.argeo.cms.ux.widgets; import java.util.IdentityHashMap; import java.util.function.Consumer; -public abstract class AbstractDataPart implements DataPart { +public abstract class AbstractDataPart implements DataPart { + private Consumer onSelected; + private Consumer onAction; - private Consumer onSelected; - private Consumer onAction; - - private IdentityHashMap, Object> views = new IdentityHashMap<>(); + private IdentityHashMap, Object> views = new IdentityHashMap<>(); private INPUT data; @@ -24,38 +23,43 @@ public abstract class AbstractDataPart implements DataPart { } @Override - public void onSelected(Consumer onSelected) { + public void onSelected(Consumer onSelected) { this.onSelected = onSelected; } @Override - public void onAction(Consumer onAction) { + public void onAction(Consumer onAction) { this.onAction = onAction; } - public Consumer getOnSelected() { + public Consumer getOnSelected() { return onSelected; } - public Consumer getOnAction() { + public Consumer getOnAction() { return onAction; } @Override public void refresh() { - for (DataView view : views.keySet()) { + for (DataView view : views.keySet()) { view.refresh(); } } + protected void notifyItemCountChange() { + for (DataView view : views.keySet()) { + view.notifyItemCountChange(); + } + } + @Override - public void addView(DataView view) { + public void addView(DataView view) { views.put(view, new Object()); } @Override - public void removeView(DataView view) { + public void removeView(DataView view) { views.remove(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 index dd1854d39..ccdcf4ea5 100644 --- 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 @@ -1,5 +1,5 @@ package org.argeo.cms.ux.widgets; -public abstract class AbstractHierarchicalPart extends AbstractDataPart implements HierarchicalPart { +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 index 7e63ea85b..835bc7ec7 100644 --- 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 @@ -1,25 +1,6 @@ package org.argeo.cms.ux.widgets; -import java.util.ArrayList; -import java.util.List; +public abstract class AbstractTabularPart extends AbstractColumnsPart + implements TabularPart { -public abstract class AbstractTabularPart extends AbstractDataPart implements TabularPart { - - 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); - } - - 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/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 index 973fddb5a..71cd263f8 100644 --- 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 @@ -2,14 +2,16 @@ package org.argeo.cms.ux.widgets; import org.argeo.api.cms.ux.CmsIcon; -public interface Column { - String getText(T model); +/** A column in a data representation. */ +@FunctionalInterface +public interface Column { + String getText(TYPE model); default int getWidth() { return 200; } - default CmsIcon getIcon(T model) { + 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 index a60401c1f..2aaeb49de 100644 --- 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 @@ -1,5 +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 index 11a12cf7b..9d3ca33ff 100644 --- 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 @@ -2,22 +2,27 @@ package org.argeo.cms.ux.widgets; import java.util.function.Consumer; -public interface DataPart { +public interface DataPart { void setInput(INPUT data); INPUT getInput(); - void onSelected(Consumer onSelected); + void onSelected(Consumer onSelected); - Consumer getOnSelected(); + Consumer getOnSelected(); - void onAction(Consumer onAction); + void onAction(Consumer onAction); - Consumer getOnAction(); + Consumer getOnAction(); void refresh(); - void addView(DataView view); + void addView(DataView view); + + void removeView(DataView view); + +// void select(TYPE data); +// +// TYPE getSelected(); - void removeView(DataView view); } 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 index 9768c68ec..311cf924e 100644 --- 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 @@ -2,4 +2,6 @@ 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/HierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java index 372c295dc..8f0e79845 100644 --- 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 @@ -2,15 +2,7 @@ package org.argeo.cms.ux.widgets; import java.util.List; -import org.argeo.api.cms.ux.CmsIcon; - +/** A hierarchical representation of data. */ public interface HierarchicalPart extends ColumnsPart { List getChildren(T parent); - - String getText(T model); - - default CmsIcon getIcon(T model) { - return null; - } - } 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 index 6adc0c3e6..01b4d6b6b 100644 --- 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 @@ -1,11 +1,8 @@ package org.argeo.cms.ux.widgets; -public interface TabularPart extends ColumnsPart { +/** A tabular presentation of data. */ +public interface TabularPart extends ColumnsPart { int getItemCount(); - T getData(int row); - - Column getColumn(int index); - - int getColumnCount(); + TYPE getData(int row); } diff --git a/org.argeo.cms/META-INF/native-image/jni-config.json b/org.argeo.cms/META-INF/native-image/jni-config.json deleted file mode 100644 index 25530bb80..000000000 --- a/org.argeo.cms/META-INF/native-image/jni-config.json +++ /dev/null @@ -1,33 +0,0 @@ -[ -{ - "name":"java.lang.Boolean", - "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.ClassLoader", - "methods":[ - {"name":"getPlatformClassLoader","parameterTypes":[] }, - {"name":"loadClass","parameterTypes":["java.lang.String"] } - ] -}, -{ - "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" -}, -{ - "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", - "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] -}, -{ - "name":"sun.management.VMManagementImpl", - "fields":[ - {"name":"compTimeMonitoringSupport"}, - {"name":"currentThreadCpuTimeSupport"}, - {"name":"objectMonitorUsageSupport"}, - {"name":"otherThreadCpuTimeSupport"}, - {"name":"remoteDiagnosticCommandsSupport"}, - {"name":"synchronizerUsageSupport"}, - {"name":"threadAllocatedMemorySupport"}, - {"name":"threadContentionMonitoringSupport"} - ] -} -] diff --git a/org.argeo.cms/META-INF/native-image/predefined-classes-config.json b/org.argeo.cms/META-INF/native-image/predefined-classes-config.json deleted file mode 100644 index 0e79b2c5d..000000000 --- a/org.argeo.cms/META-INF/native-image/predefined-classes-config.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "type":"agent-extracted", - "classes":[ - ] - } -] - diff --git a/org.argeo.cms/META-INF/native-image/proxy-config.json b/org.argeo.cms/META-INF/native-image/proxy-config.json deleted file mode 100644 index 0d4f101c7..000000000 --- a/org.argeo.cms/META-INF/native-image/proxy-config.json +++ /dev/null @@ -1,2 +0,0 @@ -[ -] diff --git a/org.argeo.cms/META-INF/native-image/reflect-config.json b/org.argeo.cms/META-INF/native-image/reflect-config.json deleted file mode 100644 index 2c9df694a..000000000 --- a/org.argeo.cms/META-INF/native-image/reflect-config.json +++ /dev/null @@ -1,60 +0,0 @@ -[ -{ - "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"java.security.SecureRandomParameters" -}, -{ - "name":"javax.security.auth.login.Configuration$Parameters" -}, -{ - "name":"org.apache.xerces.impl.dv.dtd.DTDDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.dv.dtd.XML11DTDDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.dv.xs.ExtendedSchemaDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.dv.xs.SchemaDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.xs.XSMessageFormatter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.parsers.XIncludeAwareParserConfiguration", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.argeo.cms.auth.DataAdminLoginModule", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.ConfigFile$Spi", - "methods":[{"name":"","parameterTypes":["javax.security.auth.login.Configuration$Parameters"] }] -}, -{ - "name":"sun.security.provider.DRBG", - "methods":[{"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] -}, -{ - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -} -] diff --git a/org.argeo.cms/META-INF/native-image/resource-config.json b/org.argeo.cms/META-INF/native-image/resource-config.json deleted file mode 100644 index d5f778eb0..000000000 --- a/org.argeo.cms/META-INF/native-image/resource-config.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "resources":{ - "includes":[ - { - "pattern":"\\QMETA-INF/services/javax.xml.validation.SchemaFactory\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/DSMLv2.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/SVG.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/XForms-11-Schema.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/XMLSchema.dtd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/XMLSchema.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/cr.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/datatypes.dtd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/docbook.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/schema-for-xslt20.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/xlink.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/xml-events-attribs-1.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/xml.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/internal/runtime/jaas.cfg\\E" - } - ]}, - "bundles":[ - { - "name":"org.apache.xerces.impl.msg.XMLSchemaMessages", - "locales":[ - "en", - "und" - ] - }, - { - "name":"org.apache.xerces.impl.xpath.regex.message", - "locales":[ - "", - "en", - "und" - ] - }, - { - "name":"sun.security.util.Resources", - "classNames":["sun.security.util.Resources"] - } - ] -} diff --git a/org.argeo.cms/META-INF/native-image/serialization-config.json b/org.argeo.cms/META-INF/native-image/serialization-config.json deleted file mode 100644 index bf554e062..000000000 --- a/org.argeo.cms/META-INF/native-image/serialization-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "types":[ - ], - "lambdaCapturingTypes":[ - ] -} diff --git a/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml index 6b05a9004..afd4e0aaf 100644 --- a/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml +++ b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml @@ -4,6 +4,6 @@ - + diff --git a/org.argeo.cms/OSGI-INF/cmsContentRepository.xml b/org.argeo.cms/OSGI-INF/cmsContentRepository.xml index b7a13b4d1..306717b1c 100644 --- a/org.argeo.cms/OSGI-INF/cmsContentRepository.xml +++ b/org.argeo.cms/OSGI-INF/cmsContentRepository.xml @@ -8,5 +8,5 @@ - + diff --git a/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml b/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml index 50a9ea6eb..52c75318e 100644 --- a/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml +++ b/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml @@ -2,8 +2,8 @@ - - + + diff --git a/org.argeo.cms/OSGI-INF/cmsUserManager.xml b/org.argeo.cms/OSGI-INF/cmsUserManager.xml index d22660c9f..d76c89a1b 100644 --- a/org.argeo.cms/OSGI-INF/cmsUserManager.xml +++ b/org.argeo.cms/OSGI-INF/cmsUserManager.xml @@ -1,9 +1,9 @@ - + - + - + diff --git a/org.argeo.cms/OSGI-INF/transactionManager.xml b/org.argeo.cms/OSGI-INF/transactionManager.xml index 81997476e..df317e937 100644 --- a/org.argeo.cms/OSGI-INF/transactionManager.xml +++ b/org.argeo.cms/OSGI-INF/transactionManager.xml @@ -1,8 +1,8 @@ - + - - + + 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/CmsUserManager.java b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java deleted file mode 100644 index 728884b52..000000000 --- a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.cms; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.security.auth.Subject; - -import org.argeo.cms.auth.SystemRole; -import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.directory.HierarchyUnit; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Group; -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 */ - User 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) throws InvalidSyntaxException; - - /** 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. */ - User createUser(String username, Map properties, Map credentials); - - /** Creates a group. */ - Group getOrCreateGroup(HierarchyUnit groups, String commonName); - - /** Creates a new system role. */ - Group getOrCreateSystemRole(HierarchyUnit roles, SystemRole 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(Group 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. - */ - 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); - - 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.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 4bfda139d..8aca8768a 100644 --- a/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/LocaleUtils.java @@ -4,7 +4,6 @@ import java.util.Locale; import java.util.ResourceBundle; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.CurrentUser; /** Utilities simplifying the development of localization enums. */ public class LocaleUtils { 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 index ce05dc14c..16f39609e 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java @@ -17,7 +17,7 @@ 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.util.LangUtils; +import org.argeo.cms.util.LangUtils; /** Partial reference implementation of a {@link ProvidedContent}. */ public abstract class AbstractContent extends AbstractMap implements ProvidedContent { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java index a2d8069dd..c1f1ef5f3 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java @@ -17,10 +17,12 @@ 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; @@ -93,8 +95,9 @@ public abstract class AbstractContentRepository implements ProvidedRepository { } } - public void registerTypes(String prefix, String namespaceURI, String schemaSystemId) { - typesManager.registerTypes(prefix, namespaceURI, schemaSystemId); + @Override + public void registerTypes(ContentNamespace... namespaces) { + typesManager.registerTypes(namespaces); } /* @@ -117,7 +120,7 @@ public abstract class AbstractContentRepository implements ProvidedRepository { // document = dBuilder.parse(inputSource); // } else { document = dBuilder.newDocument(); - Element root = document.createElementNS(CrName.CR_NAMESPACE_URI, + Element root = document.createElementNS(ArgeoNamespace.CR_NAMESPACE_URI, NamespaceUtils.toPrefixedName(CrName.root.qName())); for (String prefix : RuntimeNamespaceContext.getPrefixes().keySet()) { 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 474e07232..3b47c1630 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java @@ -15,9 +15,9 @@ 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.auth.CurrentUser; +import org.argeo.cms.CurrentUser; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.util.CurrentSubject; +import org.argeo.cms.util.CurrentSubject; /** * Multi-session {@link ProvidedRepository}, integrated with a CMS. diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java index 34d683605..17a03fdc5 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java @@ -12,7 +12,6 @@ import javax.security.auth.Subject; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; -import org.argeo.api.acr.CrName; import org.argeo.api.acr.DName; import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; @@ -119,23 +118,8 @@ class CmsContentSession implements ProvidedSession { } /* - * NAMESPACE CONTEXT + * EDITION */ - -// @Override -// public String getNamespaceURI(String prefix) { -// return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix); -//// return NamespaceUtils.getNamespaceURI((p) -> contentRepository.getTypesManager().getPrefixes().get(p), prefix); -// } -// -// @Override -// public Iterator getPrefixes(String namespaceURI) { -// return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI); -//// return NamespaceUtils.getPrefixes((ns) -> contentRepository.getTypesManager().getPrefixes().entrySet().stream() -//// .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), -//// namespaceURI); -// } - @Override public CompletionStage edit(Consumer work) { edition = CompletableFuture.supplyAsync(() -> { @@ -172,7 +156,7 @@ class CmsContentSession implements ProvidedSession { return uuid; } - @Override +// @Override public Content getSessionRunDir() { if (sessionRunDir == null) { String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString(); @@ -181,30 +165,9 @@ class CmsContentSession implements ProvidedSession { else { Content runDir = get(CmsContentRepository.RUN_BASE); // TODO deal with no run dir available? - sessionRunDir = runDir.add(uuid.toString(),DName.collection.qName()); + sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName()); } } return sessionRunDir; } - -// @Override -// public String findNamespace(String prefix) { -// return prefixes.get(prefix); -// } -// -// @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; -// } -// -// @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); -// } - } \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java deleted file mode 100644 index fff40c1bb..000000000 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.argeo.cms.acr; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Objects; - -import org.argeo.api.acr.CrName; - -public enum CmsContentTypes { - // - // ARGEO - // - CR_2(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI, "cr.xsd", null), - // - SLC("slc", "http://www.argeo.org/ns/slc", null, null), - // - ARGEO_LEGACY("argeo", "http://www.argeo.org/ns/argeo", null, null), - // - // EXTERNAL - // - XSD_2001("xs", "http://www.w3.org/2001/XMLSchema", "XMLSchema.xsd", "http://www.w3.org/2001/XMLSchema.xsd"), - // - XML_1998("xml", "http://www.w3.org/XML/1998/namespace", "xml.xsd", "http://www.w3.org/2001/xml.xsd"), - // - XLINK_1999("xlink", "http://www.w3.org/1999/xlink", "xlink.xsd", "http://www.w3.org/XML/2008/06/xlink.xsd"), - // - WEBDAV("D", "DAV:", null, "https://raw.githubusercontent.com/lookfirst/sardine/master/webdav.xsd"), - // - XSLT_2_0("xsl", "http://www.w3.org/1999/XSL/Transform", "schema-for-xslt20.xsd", - "https://www.w3.org/2007/schema-for-xslt20.xsd"), - // - SVG_1_1("svg", "http://www.w3.org/2000/svg", "SVG.xsd", - "https://raw.githubusercontent.com/oreillymedia/HTMLBook/master/schema/svg/SVG.xsd"), - // - DOCBOOK_5_0_1("dbk", "http://docbook.org/ns/docbook", "docbook.xsd", - "http://docbook.org/xml/5.0.1/xsd/docbook.xsd"), - // - XML_EVENTS_2001("ev", "http://www.w3.org/2001/xml-events", "xml-events-attribs-1.xsd", - "http://www.w3.org/MarkUp/SCHEMA/xml-events-attribs-1.xsd"), - // - XFORMS_2002("xforms", "http://www.w3.org/2002/xforms", "XForms-11-Schema.xsd", - "https://www.w3.org/MarkUp/Forms/2007/XForms-11-Schema.xsd"), - // - DSML_v2("dsml", "urn:oasis:names:tc:DSML:2:0:core", "DSMLv2.xsd", - "https://www.oasis-open.org/committees/dsml/docs/DSMLv2.xsd"), - // - // JCR (to be moved elsewhere) - // - JCR("jcr", "http://www.jcp.org/jcr/1.0", null, - "https://jackrabbit.apache.org/archive/wiki/JCR/NamespaceRegistry_115513459.html"), - // - JCR_MIX("mix", "http://www.jcp.org/jcr/mix/1.0", null, - "https://jackrabbit.apache.org/archive/wiki/JCR/NamespaceRegistry_115513459.html"), - // - JCR_NT("nt", "http://www.jcp.org/jcr/nt/1.0", null, - "https://jackrabbit.apache.org/archive/wiki/JCR/NamespaceRegistry_115513459.html"), - // - JACKRABBIT("rep", "internal", null, - "https://jackrabbit.apache.org/archive/wiki/JCR/NamespaceRegistry_115513459.html"), - // - JCRX("jcrx", "http://www.argeo.org/ns/jcrx", null, null), - // - ; - - private final static String RESOURCE_BASE = "/org/argeo/cms/acr/schemas/"; - - private String defaultPrefix; - private String namespace; - private URL resource; - private URL publicUrl; - - CmsContentTypes(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); - } - } - - public String getDefaultPrefix() { - return defaultPrefix; - } - - public String getNamespace() { - return namespace; - } - - public URL getResource() { - return resource; - } - - public URL getPublicUrl() { - return publicUrl; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java index 74e93fc0a..d324ac475 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java @@ -15,11 +15,11 @@ 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.cms.CmsUserManager; -import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.CurrentSubject; -import org.argeo.util.directory.Directory; -import org.argeo.util.directory.HierarchyUnit; +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}. */ @@ -135,7 +135,7 @@ public class ContentUtils { } public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) { - Directory directory = hierarchyUnit.getDirectory(); + CmsDirectory directory = hierarchyUnit.getDirectory(); StringJoiner relativePath = new StringJoiner(SLASH_STRING); buildHierarchyUnitPath(hierarchyUnit, relativePath); String path = directoryPath(directory) + relativePath.toString(); @@ -143,8 +143,8 @@ public class ContentUtils { return content; } - /** The path to this {@link Directory}. Ends with a /. */ - private static String directoryPath(Directory directory) { + /** The path to this {@link CmsDirectory}. Ends with a /. */ + private static String directoryPath(CmsDirectory directory) { return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH; } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java index 5e7c191e2..b9b940f05 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java @@ -10,11 +10,11 @@ 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; -import org.argeo.util.naming.LdapAttrs; /** * A standalone {@link ProvidedRepository} with a single {@link Subject} (which @@ -86,7 +86,7 @@ public class SingleUserContentRepository extends AbstractContentRepository { public static void main(String... args) { Path homePath = Paths.get(System.getProperty("user.home")); String username = System.getProperty("user.name"); - X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + ",dc=localhost"); + X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + ",dc=localhost"); Subject subject = new Subject(); subject.getPrincipals().add(principal); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java index 0d3838732..61d8e0429 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java @@ -20,12 +20,12 @@ public enum SvgAttrs implements QNamed { @Override public String getNamespace() { - return CmsContentTypes.SVG_1_1.getNamespace(); + return CmsContentNamespace.SVG.getNamespaceURI(); } @Override public String getDefaultPrefix() { - return CmsContentTypes.SVG_1_1.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 index d3617e128..c3bea5b60 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java @@ -1,6 +1,7 @@ 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; @@ -43,6 +44,7 @@ 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; @@ -57,7 +59,7 @@ class TypesManager { private SchemaFactory schemaFactory; /** Schema sources. */ - private List sources = new ArrayList<>(); + private List sources = new ArrayList<>(); // cached private Schema schema; @@ -65,7 +67,8 @@ class TypesManager { private XSModel xsModel; private SortedMap> types; - private boolean validating = true; + private boolean validating = false; + private boolean creatingXsModel = false; private final static boolean limited = false; @@ -78,29 +81,24 @@ class TypesManager { } public void init() { - for (CmsContentTypes cs : CmsContentTypes.values()) { - if (cs.getResource() != null) { - StreamSource source = new StreamSource(cs.getResource().toExternalForm()); - sources.add(source); - } - RuntimeNamespaceContext.register(cs.getNamespace(), cs.getDefaultPrefix()); - } - - reload(); + registerTypes(CmsContentNamespace.values()); } - public void registerTypes(String defaultPrefix, String namespace, String xsdSystemId) { + 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); - RuntimeNamespaceContext.register(namespace, defaultPrefix); + for (ContentNamespace contentNamespace : namespaces) { + RuntimeNamespaceContext.register(contentNamespace.getNamespaceURI(), contentNamespace.getDefaultPrefix()); - if (xsdSystemId != null) { - sources.add(new StreamSource(xsdSystemId)); - reload(); - log.debug(() -> "Registered types " + namespace + " from " + xsdSystemId); + if (contentNamespace.getSchemaResource() != null) { + sources.add(contentNamespace.getSchemaResource()); + log.debug(() -> "Registered types " + contentNamespace.getNamespaceURI() + " from " + + contentNamespace.getSchemaResource().toExternalForm()); + } } + reload(); } public Set listTypes() { @@ -116,7 +114,21 @@ class TypesManager { private synchronized void reload() { try { // schema - schema = schemaFactory.newSchema(sources.toArray(new Source[sources.size()])); + 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 @@ -124,31 +136,37 @@ class TypesManager { documentBuilderFactory.setNamespaceAware(true); if (!limited) { documentBuilderFactory.setXIncludeAware(true); - documentBuilderFactory.setSchema(getSchema()); - documentBuilderFactory.setValidating(validating); + if (validating) { + documentBuilderFactory.setSchema(getSchema()); + documentBuilderFactory.setValidating(validating); + } } - // XS model - // TODO use JVM implementation? + 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 (Source source : sources) { - systemIds.add(source.getSystemId()); - } - StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size()); - xsModel = xsLoader.loadURIList(sl); + 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 + // 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(); + collectTypes(); + + log.debug("Created XS model"); + } } catch (XSException | SAXException e) { throw new IllegalStateException("Cannot reload types", e); } @@ -412,34 +430,35 @@ class TypesManager { } public void printTypes() { - 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); + 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); } - } catch (ClassCastException | XSException e) { - throw new RuntimeException(e); - } } @@ -462,9 +481,9 @@ class TypesManager { // return prefixes; // } - public List getSources() { - return sources; - } +// 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 index 1313704f0..96e6eeaf3 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java @@ -17,7 +17,7 @@ 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.util.http.HttpStatus; +import org.argeo.cms.http.HttpStatus; public class DavContent extends AbstractContent { private final DavContentProvider provider; 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 index bc4bbfed3..374fcebbc 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java @@ -10,7 +10,7 @@ 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.util.http.HttpStatus; +import org.argeo.cms.http.HttpStatus; public class DavContentProvider implements ContentProvider { private String mountPath; 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 index 2d337c0dd..b737b50a1 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java @@ -10,15 +10,15 @@ 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.CrName; 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; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; abstract class AbstractDirectoryContent extends AbstractContent { protected final DirectoryContentProvider provider; @@ -50,11 +50,11 @@ abstract class AbstractDirectoryContent extends AbstractContent { Set keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR); keys: for (Enumeration it = properties.keys(); it.hasMoreElements();) { String key = it.nextElement(); - if (key.equalsIgnoreCase(LdapAttrs.objectClass.name())) + if (key.equalsIgnoreCase(LdapAttr.objectClass.name())) continue keys; - if (key.equalsIgnoreCase(LdapAttrs.objectClasses.name())) + if (key.equalsIgnoreCase(LdapAttr.objectClasses.name())) continue keys; - ContentName name = new ContentName(CrName.LDAP_NAMESPACE_URI, key, provider); + ContentName name = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, key, provider); keys.add(name); } return keys; @@ -64,16 +64,16 @@ abstract class AbstractDirectoryContent extends AbstractContent { public List getContentClasses() { Dictionary properties = doGetProperties(); List contentClasses = new ArrayList<>(); - String objectClass = properties.get(LdapAttrs.objectClass.name()).toString(); - contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, objectClass, provider)); + String objectClass = properties.get(LdapAttr.objectClass.name()).toString(); + contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, objectClass, provider)); - String[] objectClasses = properties.get(LdapAttrs.objectClasses.name()).toString().split("\\n"); + String[] objectClasses = properties.get(LdapAttr.objectClasses.name()).toString().split("\\n"); objectClasses: for (String oc : objectClasses) { - if (LdapObjs.top.name().equalsIgnoreCase(oc)) + if (LdapObj.top.name().equalsIgnoreCase(oc)) continue objectClasses; if (objectClass.equalsIgnoreCase(oc)) continue objectClasses; - contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, oc, provider)); + contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, oc, provider)); } return contentClasses; } @@ -81,8 +81,7 @@ abstract class AbstractDirectoryContent extends AbstractContent { @Override public Object put(QName key, Object value) { Object previous = get(key); - // TODO deal with typing - doGetProperties().put(key.getLocalPart(), value); + provider.getUserManager().edit(() -> doGetProperties().put(key.getLocalPart(), value)); return previous; } 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 index 4e738ae2b..6e39280ce 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java @@ -10,13 +10,13 @@ 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.util.directory.Directory; -import org.argeo.util.directory.HierarchyUnit; +import org.argeo.api.cms.directory.CmsDirectory; +import org.argeo.api.cms.directory.HierarchyUnit; class DirectoryContent extends AbstractDirectoryContent { - private Directory directory; + private CmsDirectory directory; - public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, Directory directory) { + public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, CmsDirectory directory) { super(session, provider); this.directory = directory; } @@ -45,4 +45,14 @@ class DirectoryContent extends AbstractDirectoryContent { 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 index aab5d6dc0..8b6eb6bbd 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java @@ -7,18 +7,18 @@ 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.CrName; 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.CmsUserManager; +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.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.directory.HierarchyUnit; import org.osgi.service.useradmin.User; public class DirectoryContentProvider implements ContentProvider { @@ -92,22 +92,26 @@ public class DirectoryContentProvider implements ContentProvider { @Override public String getNamespaceURI(String prefix) { - if (CrName.LDAP_DEFAULT_PREFIX.equals(prefix)) - return CrName.LDAP_NAMESPACE_URI; - throw new IllegalArgumentException("Only prefix " + CrName.LDAP_DEFAULT_PREFIX + " is supported"); + 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 (CrName.LDAP_NAMESPACE_URI.equals(namespaceURI)) - return Collections.singletonList(CrName.LDAP_DEFAULT_PREFIX).iterator(); - throw new IllegalArgumentException("Only namespace URI " + CrName.LDAP_NAMESPACE_URI + " is supported"); + 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); } 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 index 1e4aad773..5acf8ab63 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java @@ -13,9 +13,9 @@ 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.osgi.useradmin.UserDirectory; -import org.argeo.util.directory.Directory; -import org.argeo.util.directory.HierarchyUnit; +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 { @@ -46,7 +46,7 @@ class HierarchyUnitContent extends AbstractDirectoryContent { @Override public Content getParent() { HierarchyUnit parentHu = hierarchyUnit.getParent(); - if (parentHu instanceof Directory) { + if (parentHu instanceof CmsDirectory) { return new DirectoryContent(getSession(), provider, hierarchyUnit.getDirectory()); } return new HierarchyUnitContent(getSession(), provider, parentHu); 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 index 7aa144633..64feb1d67 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java @@ -7,10 +7,8 @@ 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.osgi.useradmin.UserDirectory; -import org.osgi.service.useradmin.Group; +import org.argeo.api.cms.directory.UserDirectory; import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; class RoleContent extends AbstractDirectoryContent { @@ -43,11 +41,7 @@ class RoleContent extends AbstractDirectoryContent { @SuppressWarnings("unchecked") @Override public A adapt(Class clss) { - if (clss.equals(Group.class)) - return (A) role; - else if (clss.equals(User.class)) - return (A) role; - else if (clss.equals(Role.class)) + 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 2d30cedec..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 @@ -44,7 +44,7 @@ import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.api.cms.CmsLog; import org.argeo.cms.acr.AbstractContent; import org.argeo.cms.acr.ContentUtils; -import org.argeo.util.FsUtils; +import org.argeo.cms.util.FsUtils; /** Content persisted as a filesystem {@link Path}. */ public class FsContent extends AbstractContent implements ProvidedContent { 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 59a4d8deb..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 @@ -13,8 +13,8 @@ import java.util.Objects; import java.util.TreeMap; import java.util.stream.Collectors; +import org.argeo.api.acr.ArgeoNamespace; import org.argeo.api.acr.ContentResourceException; -import org.argeo.api.acr.CrName; import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.acr.RuntimeNamespaceContext; import org.argeo.api.acr.spi.ContentProvider; @@ -62,10 +62,10 @@ public class FsContentProvider implements ContentProvider { } // defaults - addDefaultNamespace(udfav, CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI); - addDefaultNamespace(udfav, "basic", CrName.CR_NAMESPACE_URI); - addDefaultNamespace(udfav, "owner", CrName.CR_NAMESPACE_URI); - addDefaultNamespace(udfav, "posix", CrName.CR_NAMESPACE_URI); + 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); } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/XForms-11-Schema.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XForms-11-Schema.xsd deleted file mode 100644 index 881bfcb97..000000000 --- a/org.argeo.cms/src/org/argeo/cms/acr/schemas/XForms-11-Schema.xsd +++ /dev/null @@ -1,1571 +0,0 @@ - - - - - - - - - - Attributes for _every_ element in XForms - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - instance container. - - - - - - - - - - - - - - Definition of bind container. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Attributes for _every_ action in XForms - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - submit info container. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This type defines the basic lexical properties for a dataypte that can be used to represent - various ID numbers such as for debit and credit cards. - This type does not apply the Luhn checksum algorithm. - - - - - - - - - diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/docbook.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/docbook.xsd deleted file mode 100644 index f2c9aed24..000000000 --- a/org.argeo.cms/src/org/argeo/cms/acr/schemas/docbook.xsd +++ /dev/null @@ -1,17461 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 index e9ce6355a..199a469bd 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd @@ -1,293 +1,280 @@ - - - - - This schema is not normative, or even definitive. The -prose copy in the XLink 1.1 recommendation (http://www.w3.org/TR/xlink11/) is -definitive, although it should not differ from this file, except for the -absence of these two initial comments. - - - - In keeping with the W3C's standard versioning - policy, this schema document will persist at - http://www.w3.org/XML/2008/06/xlink.xsd. - At the date of issue it can also be found at - http://www.w3.org/1999/xlink.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 XLink namespace itself. In other words, if the XML - Schema or XLink namespaces change, the version of this document at - http://www.w3.org/1999/xlink.xsd will change - accordingly; the version at - http://www.w3.org/2008/06/xlink.xsd will not change. - - - - - 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 - - - - - - - - - - - - - - - - - + + + + + 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-events-attribs-1.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml-events-attribs-1.xsd deleted file mode 100644 index ef99128e9..000000000 --- a/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml-events-attribs-1.xsd +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - This is the XML Schema for XML Events global attributes - - URI: http://www.w3.org/MarkUp/SCHEMA/xml-events-attribs-1.xsd - $Id: xml-events-attribs-1.xsd,v 1.7 2004/11/22 17:09:15 ahby Exp $ - - - - - - - XML Event Attributes - - These "global" event attributes are defined in "Attaching - Attributes Directly to the Observer Element" of the XML - Events specification. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 ea1a17d7e..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,6 +3,7 @@ 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; @@ -52,7 +53,7 @@ class ElementIterator implements Iterator { if (nextElement == null) throw new NoSuchElementException(); Content result; - String isMount = nextElement.getAttributeNS(CrName.CR_NAMESPACE_URI, CrName.mount.qName().getLocalPart()); + String isMount = nextElement.getAttributeNS(ArgeoNamespace.CR_NAMESPACE_URI, CrName.mount.qName().getLocalPart()); if (isMount.equals("true")) { result = session.get(parent.getPath() + '/' + nextElement.getTagName()); } 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 index 7540455f7..42923997d 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java @@ -13,11 +13,13 @@ 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.TransformerConfigurationException; 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; @@ -34,7 +36,9 @@ public class XmlNormalizer { // private final static Logger logger = System.getLogger(XmlNormalizer.class.getName()); private DocumentBuilder documentBuilder; - private Transformer transformer; + private TransformerFactory transformerFactory; + + private DOMSource stripSpaceXsl; public XmlNormalizer() { this(2); @@ -43,12 +47,14 @@ public class XmlNormalizer { public XmlNormalizer(int indent) { try { documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder(); - TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory = TransformerFactory.newInstance(); transformerFactory.setAttribute("indent-number", indent); try (InputStream in = XmlNormalizer.class.getResourceAsStream("stripSpaces.xsl")) { - transformer = transformerFactory.newTransformer(new StreamSource(in)); + DOMResult result = new DOMResult(); + transformerFactory.newTransformer().transform(new StreamSource(in), result); + stripSpaceXsl = new DOMSource(result.getNode()); } - } catch (TransformerConfigurationException | ParserConfigurationException | TransformerFactoryConfigurationError + } catch (ParserConfigurationException | TransformerFactoryConfigurationError | TransformerException | IOException e) { throw new IllegalStateException("Cannot initialise document builder and transformer", e); } @@ -69,11 +75,20 @@ public class XmlNormalizer { } } - public void normalizeAndIndent(InputStream in, OutputStream out) throws IOException { - normalizeAndIndent(in, out, 2); + 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, int indent) throws IOException { + public void normalizeAndIndent(InputStream in, OutputStream out) throws IOException { try { Document document = documentBuilder.parse(in); @@ -88,17 +103,21 @@ public class XmlNormalizer { // node.getParentNode().removeChild(node); // } - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - // transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - - transformer.transform(new DOMSource(document), new StreamResult(out)); - } catch (DOMException | IllegalArgumentException | SAXException | TransformerFactoryConfigurationError - | TransformerException e) { + 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(); 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 d7d6c282c..289f8dcc6 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -27,7 +27,7 @@ import org.argeo.cms.internal.auth.CmsSessionImpl; import org.argeo.cms.internal.auth.ImpliedByPrincipal; import org.argeo.cms.internal.auth.RemoteCmsSessionImpl; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.osgi.useradmin.AuthenticatingUser; +import org.argeo.cms.osgi.useradmin.AuthenticatingUser; import org.osgi.service.useradmin.Authorization; /** Centralises security related registrations. */ diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java index 6aa1c1c25..8834f3587 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java @@ -2,24 +2,27 @@ 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.api.acr.CrName; +import org.argeo.cms.SystemRole; /** Standard CMS system roles. */ public enum CmsRole implements SystemRole { userAdmin, // - groupAdmin; + groupAdmin, // + // + ; private final static String QUALIFIER = "cms."; private final ContentName name; CmsRole() { - name = new ContentName(CrName.ROLE_NAMESPACE_URI, QUALIFIER + name()); + name = new ContentName(ArgeoNamespace.ROLE_NAMESPACE_URI, QUALIFIER + name()); } @Override - public QName getName() { + public QName qName() { return name; } 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 2fd8730d8..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java +++ /dev/null @@ -1,185 +0,0 @@ -package org.argeo.cms.auth; - -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.util.CurrentSubject; -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); - } - - /** Implies this {@link SystemRole} in this context. */ - public final static boolean implies(SystemRole role, String context) { - return role.implied(currentSubject(), context); - } - - /** Implies this {@link SystemRole} in this context. */ - public final static boolean implies(String role, String context) { - return SystemRole.implied(NamespaceUtils.parsePrefixedName(role), currentSubject(), context); - } - - /** 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 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/auth/KeyringLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java index af559df75..ebab12f2c 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java @@ -14,9 +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.CurrentSubject; -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 { 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 785eeb912..3c436ba1f 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -12,10 +12,10 @@ 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.http.HttpHeader; +import org.argeo.cms.http.HttpStatus; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.util.CurrentSubject; -import org.argeo.util.http.HttpHeader; -import org.argeo.util.http.HttpStatus; +import org.argeo.cms.util.CurrentSubject; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; @@ -161,11 +161,15 @@ public class RemoteAuthUtils { // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic // realm=\"" + httpAuthRealm + "\""); - if (hasAcceptorCredentials() && !forceBasic && !negotiateFailed)// SPNEGO - remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), HttpHeader.NEGOTIATE); - else + 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 * 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 d2ceb27a5..987c3dd19 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -16,13 +16,13 @@ import javax.security.auth.spi.LoginModule; 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.CmsContextImpl; import org.argeo.cms.internal.runtime.CmsStateImpl; -import org.argeo.util.http.HttpHeader; 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); @@ -64,8 +64,6 @@ public class RemoteSessionLoginModule implements LoginModule { return false; // TODO factorize with below String httpSessionId = httpSession.getId(); -// if (log.isTraceEnabled()) -// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId); if (cmsSession != null && !cmsSession.isAnonymous()) { authorization = cmsSession.getAuthorization(); @@ -77,16 +75,8 @@ public class RemoteSessionLoginModule implements LoginModule { 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(); -// } if (httpSession != null) { String httpSessionId = httpSession.getId(); -// if (log.isTraceEnabled()) -// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId); CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId); if (cmsSession != null && !cmsSession.isAnonymous()) { authorization = cmsSession.getAuthorization(); @@ -191,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) { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java deleted file mode 100644 index bf91f3941..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.argeo.cms.auth; - -public class RoleNameUtils { - - /* - * UTILITIES - */ - public final static String getLastRdnValue(String 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 final static String getParent(String dn) { - int index = dn.indexOf(','); - return dn.substring(index + 1); - } - - /** Up two levels. */ - public final static String getContext(String dn) { - return getParent(getParent(dn)); - } -} 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 35b7d5cc7..4b36f28ab 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java @@ -11,10 +11,10 @@ import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import javax.security.auth.x500.X500Principal; +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.cms.directory.ldap.IpaUtils; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.osgi.useradmin.OsUserUtils; -import org.argeo.util.directory.ldap.IpaUtils; -import org.argeo.util.naming.LdapAttrs; +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. */ @@ -54,7 +54,7 @@ public class SingleUserLoginModule implements LoginModule { throw new LoginException("No username available"); 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(); } 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 a01daf6e0..e5f367d23 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java @@ -36,8 +36,15 @@ 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; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java b/org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java deleted file mode 100644 index 5d62d9803..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.cms.auth; - -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; - -public interface SystemRole { - QName getName(); - - default boolean implied(Subject subject, String context) { - return implied(getName(), subject, context); - } - - 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/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 0ae84ff8a..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,13 +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.directory.ldap.IpaUtils; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.osgi.useradmin.AuthenticatingUser; -import org.argeo.osgi.useradmin.TokenUtils; -import org.argeo.util.directory.ldap.IpaUtils; -import org.argeo.util.naming.LdapAttrs; +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; @@ -47,8 +48,8 @@ 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.employeeNumber.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; @@ -155,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)) { @@ -270,8 +275,21 @@ public class UserAdminLoginModule implements LoginModule { // 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 e3eb44249..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); @@ -73,11 +74,11 @@ public class UserAdminUtils { } public static String getUserDisplayName(Role user) { - String dName = getProperty(user, LdapAttrs.displayName.name()); + String dName = getProperty(user, LdapAttr.displayName.name()); if (isEmpty(dName)) - dName = getProperty(user, LdapAttrs.cn.name()); + dName = getProperty(user, LdapAttr.cn.name()); if (isEmpty(dName)) - dName = getProperty(user, LdapAttrs.uid.name()); + dName = getProperty(user, LdapAttr.uid.name()); if (isEmpty(dName)) dName = getUserLocalId(user.getName()); return dName; @@ -92,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 @@ -125,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 { @@ -150,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/SpnegoHttpClient.java b/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java deleted file mode 100644 index 444f4efb5..000000000 --- a/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.argeo.cms.client; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.net.MalformedURLException; -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.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 javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; - -import org.argeo.cms.auth.ConsoleCallbackHandler; -import org.argeo.cms.auth.RemoteAuthUtils; -import org.argeo.util.http.HttpHeader; - -public class SpnegoHttpClient { - public final static String CLIENT_LOGIN_CONTEXT = "CLIENT"; - - public static void main(String[] args) throws MalformedURLException { -// String principal = System.getProperty("javax.security.auth.login.name"); - if (args.length == 0) { - System.err.println("usage: java -Djavax.security.auth.login.name= " - + SpnegoHttpClient.class.getName() + " "); - System.exit(1); - return; - } - String url = args[0]; - URL u = new URL(url); - String server = u.getHost(); - - URL jaasUrl = SpnegoHttpClient.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(); - - HttpClient httpClient = openHttpClient(lc.getSubject()); - String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", server); - - HttpRequest request = HttpRequest.newBuilder().uri(u.toURI()) // - .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token) // - .build(); - BodyHandler bodyHandler = BodyHandlers.ofString(); - HttpResponse response = httpClient.send(request, bodyHandler); - System.out.println(response.body()); - int responseCode = response.statusCode(); - System.exit(responseCode); - } catch (Exception e) { - e.printStackTrace(); - } - } - - static HttpClient openHttpClient(Subject subject) { - HttpClient client = HttpClient.newBuilder() // - .sslContext(ipaSslContext()) // - .version(HttpClient.Version.HTTP_1_1) // - .build(); - - return client; - } - - @SuppressWarnings("unchecked") - static 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 index 294b485fb..e8dd2fa52 100644 --- a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java @@ -1,65 +1,60 @@ package org.argeo.cms.client; import java.net.URI; -import java.net.URL; -import java.net.http.HttpClient; import java.net.http.WebSocket; import java.nio.ByteBuffer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; - -import javax.security.auth.login.LoginContext; - -import org.argeo.cms.auth.RemoteAuthUtils; -import org.argeo.util.http.HttpHeader; +import java.util.concurrent.ExecutionException; /** Tests connectivity to the web socket server. */ -public class WebSocketEventClient { +public class WebSocketEventClient implements Runnable { - public static void main(String[] args) throws Exception { - if (args.length == 0) { - System.err.println("usage: java " + WebSocketEventClient.class.getName() + " "); - System.exit(1); - return; - } - URI uri = URI.create(args[0]); - WebSocket.Listener listener = new WebSocket.Listener() { + private final URI uri; - public CompletionStage onText(WebSocket webSocket, CharSequence message, boolean last) { - System.out.println(message); - CompletionStage res = CompletableFuture.completedStage(message.toString()); - return res; - } + private WebSocket webSocket; - @Override - public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { - // System.out.println("Pong received."); - return null; - } - - }; + private CmsClient cmsClient; - // SPNEGO - URL jaasUrl = SpnegoHttpClient.class.getResource("jaas-client-ipa.cfg"); - System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm()); - LoginContext lc = new LoginContext(SpnegoHttpClient.CLIENT_LOGIN_CONTEXT); - lc.login(); - String token = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", uri.getHost()); + public WebSocketEventClient(URI uri) { + this.uri = uri; + cmsClient = new CmsClient(uri); + } - HttpClient client = SpnegoHttpClient.openHttpClient(lc.getSubject()); - CompletableFuture ws = client.newWebSocketBuilder() - .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + token) - .buildAsync(uri, listener); + @Override + public void run() { + try { + CompletableFuture ws = cmsClient.newWebSocket(new WsEventListener()); - WebSocket webSocket = ws.get(); - webSocket.request(Long.MAX_VALUE); + WebSocket webSocket = ws.get(); + webSocket.request(Long.MAX_VALUE); - Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""))); + Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, ""))); - while (!webSocket.isInputClosed()) { - webSocket.sendPing(ByteBuffer.allocate(0)); - Thread.sleep(10000); + 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/WsPing.java b/org.argeo.cms/src/org/argeo/cms/client/WsPing.java deleted file mode 100644 index 55d6f047f..000000000 --- a/org.argeo.cms/src/org/argeo/cms/client/WsPing.java +++ /dev/null @@ -1,85 +0,0 @@ -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 WsPing implements Runnable { - private final static int PING_FRAME_SIZE = 125; - - private final URI uri; - private final UUID uuid; - - private final DecimalFormat decimalFormat; - - public WsPing(URI uri) { - this.uri = uri; - this.uuid = UUID.randomUUID(); - decimalFormat = new DecimalFormat("0.0"); - decimalFormat.setRoundingMode(RoundingMode.HALF_UP); - } - - @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 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 | ExecutionException e) { - e.printStackTrace(); - } - } - - 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 index 2921a3397..b776c2c5c 100644 --- 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 @@ -1,4 +1,4 @@ CLIENT { com.sun.security.auth.module.Krb5LoginModule required - useTicketCache=true; + 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 index 94f292c8e..6fe2eb617 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java @@ -15,9 +15,9 @@ import java.util.Iterator; import javax.xml.namespace.QName; -import org.argeo.util.http.HttpHeader; -import org.argeo.util.http.HttpMethod; -import org.argeo.util.http.HttpStatus; +import org.argeo.cms.http.HttpHeader; +import org.argeo.cms.http.HttpMethod; +import org.argeo.cms.http.HttpStatus; public class DavClient { diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java b/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java index 24695e7b1..c7542b55a 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java @@ -1,6 +1,6 @@ package org.argeo.cms.dav; -import org.argeo.util.http.HttpHeader; +import org.argeo.cms.http.HttpHeader; import com.sun.net.httpserver.HttpExchange; diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java index 1d6c02623..63f4f82c7 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java @@ -10,10 +10,10 @@ import java.util.function.Consumer; import javax.xml.namespace.NamespaceContext; import org.argeo.api.acr.ContentNotFoundException; -import org.argeo.util.http.HttpHeader; -import org.argeo.util.http.HttpMethod; -import org.argeo.util.http.HttpStatus; -import org.argeo.util.http.HttpServerUtils; +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; diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java index 828dc2640..8dd6bf3fc 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java @@ -10,7 +10,7 @@ import java.util.TreeSet; import javax.xml.namespace.QName; -import org.argeo.util.http.HttpStatus; +import org.argeo.cms.http.HttpStatus; /** The WebDav response for a given resource. */ public class DavResponse { diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java index 6d22c8e29..c7b54b027 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java @@ -19,7 +19,7 @@ import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import org.argeo.util.http.HttpStatus; +import org.argeo.cms.http.HttpStatus; /** * Asynchronously iterate over the response statuses of the response to a diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java index 986b2fe92..4689b8c8d 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java @@ -20,7 +20,7 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; -import org.argeo.util.http.HttpStatus; +import org.argeo.cms.http.HttpStatus; class MultiStatusWriter implements Consumer { private BlockingQueue queue = new ArrayBlockingQueue<>(64); 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/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/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/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java deleted file mode 100644 index b7445633b..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java +++ /dev/null @@ -1,712 +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.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 org.argeo.api.acr.NamespaceUtils; -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.SystemRole; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.osgi.useradmin.AggregatingUserAdmin; -import org.argeo.osgi.useradmin.TokenUtils; -import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.directory.DirectoryConf; -import org.argeo.util.directory.HierarchyUnit; -import org.argeo.util.directory.ldap.LdapEntry; -import org.argeo.util.directory.ldap.SharedSecret; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.NamingUtils; -import org.argeo.util.transaction.WorkTransaction; -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 = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(), - LdapAttrs.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) throws InvalidSyntaxException { - return userAdmin.getRoles(filter); - } - - // ALL USER: WARNING access to this will be later reduced - - /** Retrieve a user given his dn, or null if it doesn't exist. */ - 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; - } - - 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, (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.SYSTEM_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); - } - - /* - * EDITION - */ - @Override - public User createUser(String username, Map properties, Map credentials) { - try { - userTransaction.begin(); - User user = (User) 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 Group getOrCreateGroup(HierarchyUnit groups, String commonName) { - try { - String dn = LdapAttrs.cn.name() + "=" + commonName + "," + groups.getBase(); - Group group = (Group) getUserAdmin().getRole(dn); - if (group != null) - return group; - userTransaction.begin(); - group = (Group) 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 Group getOrCreateSystemRole(HierarchyUnit roles, SystemRole systemRole) { - try { - String dn = LdapAttrs.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole.getName()) + "," - + roles.getBase(); - Group group = (Group) getUserAdmin().getRole(dn); - if (group != null) - return group; - userTransaction.begin(); - group = (Group) 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(Group 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 object classes " + role + " to 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 = LdapAttrs.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn; - else if (Role.USER == type) - dn = LdapAttrs.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(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); - } - } - - @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/auth/ImpliedByPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java index 73f474637..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,16 +1,12 @@ package org.argeo.cms.internal.auth; -import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext; - import java.security.Principal; import java.util.HashSet; import java.util.Set; import javax.xml.namespace.QName; -import org.argeo.api.acr.CrName; -import org.argeo.api.acr.NamespaceUtils; -import org.argeo.cms.auth.RoleNameUtils; +import org.argeo.cms.RoleNameUtils; import org.osgi.service.useradmin.Authorization; /** @@ -23,71 +19,25 @@ import org.osgi.service.useradmin.Authorization; */ public final class ImpliedByPrincipal implements Principal { private final String name; - private Set causes = new HashSet(); - - private QName roleName; -// private int type = Role.ROLE; + private final QName roleName; + private final boolean systemRole; + private final String context; - private boolean systemRole = false; - private String context; + private Set causes = new HashSet(); public ImpliedByPrincipal(String name, Principal userPrincipal) { this.name = name; - String cn = RoleNameUtils.getLastRdnValue(name); - roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn); - if (roleName.getNamespaceURI().equals(CrName.ROLE_NAMESPACE_URI)) { - systemRole = true; - } + roleName = RoleNameUtils.getLastRdnAsName(name); + systemRole = RoleNameUtils.isSystemRole(roleName); context = RoleNameUtils.getContext(name); -// 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; -// if (userPrincipal != null) -// causes.add(userPrincipal); -// } - public String getName() { return name; } - /* - * USER ADMIN - */ -// 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); -// } -// -// -// /** Type of {@link Role}, if known. */ -// public int getType() { -// return type; -// } -// -// /** Not supported for the time being. */ -// public Dictionary getProperties() { -// throw new UnsupportedOperationException(); -// } - /* * OBJECT */ @@ -111,8 +61,6 @@ public final class ImpliedByPrincipal implements Principal { @Override public boolean equals(Object obj) { - // if (this == obj) - // return true; if (obj instanceof ImpliedByPrincipal) { ImpliedByPrincipal that = (ImpliedByPrincipal) obj; // TODO check members too? @@ -123,7 +71,6 @@ public final class ImpliedByPrincipal implements Principal { @Override public String toString() { - // return name.toString() + " implied by " + causes; return name.toString(); } } 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 index 164e9b9b2..e17a089fe 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java @@ -5,7 +5,7 @@ import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.argeo.api.cms.CmsAuth; -import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.CurrentUser; import org.argeo.cms.auth.RemoteAuthCallbackHandler; import org.argeo.cms.auth.RemoteAuthRequest; import org.argeo.cms.auth.RemoteAuthResponse; @@ -15,20 +15,14 @@ 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 { -// final static String HEADER_AUTHORIZATION = "Authorization"; -// final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - -// private final static CmsLog log = CmsLog.getLog(CmsAuthenticator.class); - // TODO make it configurable private final String httpAuthRealm = "Argeo"; private final boolean forceBasic = false; @Override public Result authenticate(HttpExchange exch) { -// if (log.isTraceEnabled()) -// HttpUtils.logRequestHeaders(log, request); RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(exch); ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader()); @@ -53,20 +47,6 @@ public class CmsAuthenticator extends Authenticator { Subject subject = lc.getSubject(); -// CurrentSubject.callAs(subject, () -> { -// RemoteAuthUtils.configureRequestSecurity(remoteAuthExchange); -// return null; -// }); -// 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; -// } -// -// }); String username = CurrentUser.getUsername(subject); HttpPrincipal httpPrincipal = new HttpPrincipal(username, httpAuthRealm); return new Authenticator.Success(httpPrincipal); 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 index 00f2b8fe1..b7e670c79 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java @@ -1,5 +1,6 @@ package org.argeo.cms.internal.http; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -22,8 +23,14 @@ public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResp } @Override - public void setHeader(String keys, String value) { - httpExchange.getResponseHeaders().put(keys, Collections.singletonList(value)); + 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 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 59b226a53..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 @@ -26,29 +26,7 @@ public class CmsActivator implements BundleActivator { private static BundleContext bundleContext; -// private LogReaderService logReaderService; -// -// private CmsOsgiLogger logger; - 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() { @@ -98,12 +76,6 @@ public class CmsActivator implements BundleActivator { } -// private void initArgeoLogger() { -// logger = new CmsOsgiLogger(logReaderService); -// if (bundleContext != null) -// bundleContext.registerService(ArgeoLogger.class, logger, null); -// } - public static void registerService(Class clss, T service, Dictionary properties) { if (bundleContext != null) { bundleContext.registerService(clss, service, properties); 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 3358ed825..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 @@ -5,7 +5,7 @@ import java.util.Enumeration; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; -import org.argeo.util.directory.DirectoryConf; +import org.argeo.cms.runtime.DirectoryConf; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; 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 index c36f410e2..c80933a55 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java @@ -25,9 +25,9 @@ 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.util.StreamUtils; -import org.argeo.util.http.HttpStatus; +import org.argeo.cms.util.StreamUtils; import com.sun.net.httpserver.HttpExchange; 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 c2d2ccf42..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 @@ -25,19 +25,18 @@ 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 static CompletableFuture instance = new CompletableFuture(); -// private static CmsContextImpl instance = null; private CmsState cmsState; private CmsDeployment cmsDeployment; private UserAdmin userAdmin; private UuidFactory uuidFactory; private CmsEventBus cmsEventBus; -// private ProvidedRepository contentRepository; // i18n private Locale defaultLocale; @@ -64,9 +63,6 @@ public class CmsContextImpl implements CmsContext { } } }, "Check readiness").start(); - - // checkReadiness(); - setInstance(this); } @@ -178,14 +174,6 @@ public class CmsContextImpl implements CmsContext { this.uuidFactory = uuidFactory; } -// public ProvidedRepository getContentRepository() { -// return contentRepository; -// } -// -// public void setContentRepository(ProvidedRepository contentRepository) { -// this.contentRepository = contentRepository; -// } - @Override public Locale getDefaultLocale() { return defaultLocale; @@ -238,15 +226,6 @@ public class CmsContextImpl implements CmsContext { } private static void setInstance(CmsContextImpl cmsContextImpl) { -// if (cmsContextImpl != null) { -// if (instance != null) -// throw new IllegalStateException("CMS Context is already set"); -// instance = cmsContextImpl; -// } else { -// instance = null; -// } -// CmsContextImpl.class.notifyAll(); - if (cmsContextImpl != null) { if (instance.isDone()) throw new IllegalStateException("CMS Context is already set"); @@ -259,15 +238,6 @@ public class CmsContextImpl implements CmsContext { } private static CmsContextImpl getInstance() { -// while (instance == null) { -// try { -// CmsContextImpl.class.wait(); -// } catch (InterruptedException e) { -// throw new IllegalStateException("Cannot wait for CMS context instance", e); -// } -// } -// return instance; - try { return instance.get(); } catch (InterruptedException | ExecutionException e) { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java index 421ff661a..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 @@ -18,7 +18,7 @@ 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()); 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 index eaa63756d..99f6c1d8d 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java @@ -10,16 +10,12 @@ 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); - // CMS events private Map>> topics = new TreeMap<>(); -// private IdentityHashMap> subscriptions = new IdentityHashMap<>(); - /* - * CMS Events - */ @Override public void sendEvent(String topic, Map event) { SubmissionPublisher> publisher = topics.get(topic); @@ -58,6 +54,7 @@ public class CmsEventBusImpl implements CmsEventBus { } } + /** A subscriber to a topic. */ static class CmsEventFlowSubscriber implements Flow.Subscriber> { private String topic; private CmsEventSubscriber eventSubscriber; 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 cb806444f..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 @@ -38,7 +38,7 @@ 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.argeo.util.FsUtils; +import org.argeo.cms.util.FsUtils; /** * Implementation of a {@link CmsState}, initialising the required services. @@ -170,9 +170,11 @@ public class CmsStateImpl implements CmsState { 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); + log.error("Cannot set permissions on " + privateDir, e); } } 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 index 7f4314b99..e6f903d39 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java @@ -29,14 +29,14 @@ 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.osgi.useradmin.AggregatingUserAdmin; -import org.argeo.osgi.useradmin.DirectoryUserAdmin; -import org.argeo.osgi.useradmin.UserDirectory; -import org.argeo.util.directory.DirectoryConf; -import org.argeo.util.naming.dns.DnsBrowser; -import org.argeo.util.transaction.WorkControl; -import org.argeo.util.transaction.WorkTransaction; +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; 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 index 8358105e2..0fd0a63ed 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java @@ -5,7 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsUserManager; +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; 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 716596648..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 @@ -55,18 +55,9 @@ class KernelUtils implements KernelConstants { Path executionDir = Paths.get(getFrameworkProp("user.dir")); if (relativePath == null) return executionDir; -// try { return executionDir.resolve(relativePath); -// } catch (IOException e) { -// throw new IllegalArgumentException("Cannot get canonical file", e); -// } } -// static File getOsgiInstanceDir() { -// return new File(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) -// .getAbsoluteFile(); -// } - public static Path getOsgiInstancePath(String relativePath) { URI uri = getOsgiInstanceUri(relativePath); if (uri == null) // no data area available @@ -81,22 +72,9 @@ class KernelUtils implements KernelConstants { if (!osgiInstanceBaseUri.endsWith("/")) osgiInstanceBaseUri = osgiInstanceBaseUri + "/"; -// if (osgiInstanceBaseUri != null) return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); -// else -// return Paths.get(System.getProperty("user.dir"), (relativePath != null ? relativePath : "")).toUri(); } -// static File getOsgiConfigurationFile(String relativePath) { -// try { -// return new File( -// new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) -// .getCanonicalFile(); -// } catch (Exception e) { -// throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e); -// } -// } - static String getFrameworkProp(String key, String def) { String value; if (CmsActivator.getBundleContext() != null) @@ -112,32 +90,10 @@ class KernelUtils implements KernelConstants { 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) { @@ -148,84 +104,6 @@ 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/osgi/BundleCmsTheme.java b/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java index 3443d73a6..f60d3352e 100644 --- a/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java +++ b/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java @@ -17,7 +17,7 @@ import java.util.TreeSet; import java.util.stream.Collectors; import org.argeo.api.cms.ux.CmsTheme; -import org.argeo.util.StreamUtils; +import org.argeo.cms.util.StreamUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; 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 index e473d2799..76775fed8 100644 --- a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java +++ b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java @@ -9,21 +9,21 @@ 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.CmsUserManager; import org.argeo.cms.acr.CmsUuidFactory; -import org.argeo.cms.internal.auth.CmsUserManagerImpl; 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.argeo.util.register.Component; -import org.argeo.util.register.ComponentRegister; -import org.argeo.util.register.SimpleRegister; -import org.argeo.util.transaction.SimpleTransactionManager; -import org.argeo.util.transaction.WorkControl; -import org.argeo.util.transaction.WorkTransaction; import org.osgi.service.useradmin.UserAdmin; /** 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 3de2e1451..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.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.util.CurrentSubject; -import org.argeo.util.StreamUtils; - -/** 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/security/ChecksumFactory.java b/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java deleted file mode 100644 index 7344f01bc..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java +++ /dev/null @@ -1,149 +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.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/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/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/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/Service.java index aa9494c91..cab85d02f 100644 --- a/org.argeo.init/src/org/argeo/init/Service.java +++ b/org.argeo.init/src/org/argeo/init/Service.java @@ -40,7 +40,7 @@ public class Service { } } catch (Exception e) { e.printStackTrace(); - System.exit(1); + Runtime.getRuntime().halt(1); } }, "Runtime shutdown")); 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 c28df3b23..5c8329c85 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Source.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Source.java @@ -4,8 +4,15 @@ 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 0c3cefd01..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); } @@ -174,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)) { @@ -219,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 5099eed15..e0e2e437f 100644 --- a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java +++ b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java @@ -6,10 +6,10 @@ 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.HashMap; import java.util.Map; import java.util.SortedMap; +import java.util.StringJoiner; import java.util.TreeMap; import org.argeo.init.osgi.OsgiBootUtils; @@ -18,15 +18,16 @@ 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 Map xOr; + private final Map variantsXOr; - public FsA2Source(Path base) { - this(base, new HashMap<>()); - } +// public FsA2Source(Path base) { +// this(base, new HashMap<>()); +// } - public FsA2Source(Path base, Map xOr) { + public FsA2Source(Path base, Map variantsXOr, boolean usingReference) { + super(usingReference); this.base = base; - this.xOr = xOr; + this.variantsXOr = new HashMap<>(variantsXOr); } void load() throws IOException { @@ -44,7 +45,7 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source { } else {// variants Path variantPath = null; // is it an explicit variant? - String variant = xOr.get(contributionPath.getFileName().toString()); + String variant = variantsXOr.get(contributionPath.getFileName().toString()); if (variant != null) { variantPath = contributionPath.resolve(variant); } @@ -92,19 +93,8 @@ 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) { + // TODO optimise? check attributes? String[] nameVersion = readNameVersionFromModule(modulePath); String componentName = nameVersion[0]; String versionStr = nameVersion[1]; @@ -130,7 +120,16 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source { URI baseUri = base.toUri(); try { if (baseUri.getScheme().equals("file")) { - return new URI(SCHEME_A2, null, base.toString(), null); + 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()); } @@ -139,19 +138,23 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source { } } - 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(); - } + 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 6d842650c..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,5 +1,8 @@ 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; @@ -9,7 +12,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -25,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. */ @@ -54,8 +55,8 @@ 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) { @@ -72,7 +73,7 @@ public class ProvisioningManager { xOr.put(key, lst.get(0)); } - if (A2Source.SCHEME_A2.equals(u.getScheme())) { + 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 @@ -80,12 +81,19 @@ public class ProvisioningManager { } Path base = Paths.get(baseStr); if (Files.exists(base)) { - FsA2Source source = new FsA2Source(base, xOr); + 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); @@ -217,46 +225,46 @@ public class ProvisioningManager { } } - 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(); - } - } - } +// 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/osgi/OsgiBoot.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java index 003718785..884461b12 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java @@ -104,6 +104,13 @@ public class OsgiBoot implements OsgiBootConstants { 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 + queryPart); } @@ -126,36 +133,6 @@ public class OsgiBoot implements OsgiBootConstants { public void bootstrap(Map properties) { try { long begin = System.currentTimeMillis(); - // check properties -// if (properties != null) { -// for (String property : properties.keySet()) { -// String value = properties.get(property); -// String bcValue = bundleContext.getProperty(property); -// if (PROP_OSGI_CONFIGURATION_AREA.equals(property) || PROP_OSGI_INSTANCE_AREA.equals(property)) { -// try { -// if (value.startsWith("/")) -// value = "file://" + value; -// URL uri = new URL(value); -// URL bcUri = new URL(bcValue); -// if (!uri.equals(bcUri)) -// throw new IllegalArgumentException("Property " + property + "=" + uri -// + " is inconsistent with bundle context : " + bcUri); -// } catch (MalformedURLException e) { -// throw new IllegalArgumentException("Malformed property " + property, e); -// } -// -// } else { -// if (!value.equals(bcValue)) -// throw new IllegalArgumentException("Property " + property + "=" + value -// + " is inconsistent with bundle context : " + bcValue); -// } -// } -// } else { -// String useSystemProperties = bundleContext.getProperty(PROP_OSGI_USE_SYSTEM_PROPERTIES); -// if (useSystemProperties == null || !useSystemProperties.equals("true")) { -// OsgiBootUtils.warn("No properties passed but " + PROP_OSGI_USE_SYSTEM_PROPERTIES + " is not set."); -// } -// } // notify start String osgiInstancePath = getProperty(PROP_OSGI_INSTANCE_AREA); @@ -174,6 +151,9 @@ public class OsgiBoot implements OsgiBootConstants { // A2 install bundles provisioningManager.install(null); + // 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); @@ -242,7 +222,7 @@ public class OsgiBoot implements OsgiBootConstants { String url = (String) urls.get(i); installUrl(url, installedBundles); } - refreshFramework(); +// refreshFramework(); } /** Actually install the provided URL */ @@ -319,15 +299,6 @@ public class OsgiBoot implements OsgiBootConstants { /* * START */ -// /** -// * Start bundles based on system properties. -// * -// * @see OsgiBoot#doStartBundles(Map) -// */ -// public void startBundles() { -// Properties properties = System.getProperties(); -// startBundles(properties); -// } /** * Start bundles based on these properties. @@ -384,8 +355,17 @@ public class OsgiBoot implements OsgiBootConstants { 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); @@ -412,13 +392,40 @@ public class OsgiBoot implements OsgiBootConstants { } } } + + if (OsgiBootUtils.isDebug()) + OsgiBootUtils.debug("About to set framework start level to " + activeStartLevel + " ..."); + frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> { - if (OsgiBootUtils.isDebug()) - 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, Map properties, @@ -468,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.isDebug()) -// 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.isDebug()) -// 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 */ @@ -691,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(); } @@ -808,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); } @@ -832,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/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.util/.classpath b/org.argeo.util/.classpath deleted file mode 100644 index 4199cd3a3..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/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/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 83b2f1709..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ /dev/null @@ -1,322 +0,0 @@ -package org.argeo.osgi.useradmin; - -import static org.argeo.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.util.directory.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 allRoles = new ArrayList<>(Arrays.asList(rawAuthorization.getRoles())); - for (LdapName otherBaseDn : businessRoles.keySet()) { - if (otherBaseDn.equals(userReferentialOfThisUser.getBaseDn())) - continue; - DirectoryUserAdmin otherUserAdmin = userAdminToUse(user, businessRoles.get(otherBaseDn)); - if (otherUserAdmin == null) - continue; - Authorization auth = otherUserAdmin.getAuthorization(retrievedUser); - 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 (user instanceof DirectoryUser) { - 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.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java deleted file mode 100644 index ba1f3f753..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.argeo.osgi.useradmin; - -import java.util.Dictionary; -import java.util.Hashtable; - -import javax.naming.ldap.LdapName; - -import org.argeo.util.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.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java deleted file mode 100644 index 1d58a2dae..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.osgi.useradmin; - -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 18b28a288..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.osgi.useradmin; - -import org.osgi.service.useradmin.User; - -/** A user in a user directory. */ -interface DirectoryUser extends User { -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java deleted file mode 100644 index 3903a23f4..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java +++ /dev/null @@ -1,401 +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.net.URI; -import java.security.PrivilegedAction; -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.util.CurrentSubject; -import org.argeo.util.directory.DirectoryConf; -import org.argeo.util.directory.DirectoryDigestUtils; -import org.argeo.util.directory.HierarchyUnit; -import org.argeo.util.directory.ldap.AbstractLdapDirectory; -import org.argeo.util.directory.ldap.LdapDao; -import org.argeo.util.directory.ldap.LdapEntry; -import org.argeo.util.directory.ldap.LdapEntryWorkingCopy; -import org.argeo.util.directory.ldap.LdapNameUtils; -import org.argeo.util.directory.ldap.LdifDao; -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(DirectoryUser 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((DirectoryUser) entry); - if (wc != null) { - for (Iterator it = res.iterator(); it.hasNext();) { - DirectoryUser user = (DirectoryUser) 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()) { - DirectoryUser user = (DirectoryUser) 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((DirectoryUser) 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 DirectoryUser) { - return new LdifAuthorization(user, getAllRoles((DirectoryUser) user)); - } else { - // bind with authenticating user - DirectoryUserAdmin scopedUserAdmin = scope(user).orElseThrow(); - return getAuthorizationFromScoped(scopedUserAdmin, user); - } - } - } - - private Authorization getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin, User user) { - try { - DirectoryUser directoryUser = (DirectoryUser) 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.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java deleted file mode 100644 index d7f6ad960..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.name()); - if (displayName == null) - displayName = props.get(LdapAttrs.cn.name()); - if (displayName == null) - displayName = props.get(LdapAttrs.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.util/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java deleted file mode 100644 index bdf34aa91..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.argeo.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.util.directory.ldap.AbstractLdapDirectory; -import org.osgi.service.useradmin.Role; - -/** Directory group implementation */ -class LdifGroup extends LdifUser implements DirectoryGroup { - 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.util/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java deleted file mode 100644 index 0b07c7565..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.argeo.osgi.useradmin; - -import javax.naming.ldap.LdapName; - -import org.argeo.util.directory.ldap.AbstractLdapDirectory; -import org.argeo.util.directory.ldap.DefaultLdapEntry; - -/** Directory user implementation */ -class LdifUser extends DefaultLdapEntry implements DirectoryUser { - 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.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java deleted file mode 100644 index 5d7e97dde..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.argeo.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.util.directory.HierarchyUnit; -import org.argeo.util.directory.ldap.AbstractLdapDirectory; -import org.argeo.util.directory.ldap.AbstractLdapDirectoryDao; -import org.argeo.util.directory.ldap.LdapEntry; -import org.argeo.util.directory.ldap.LdapEntryWorkingCopy; -import org.argeo.util.naming.LdapAttrs; - -/** 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(LdapAttrs.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.util/src/org/argeo/osgi/useradmin/OsUserUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java deleted file mode 100644 index 5d0cbf687..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java +++ /dev/null @@ -1,54 +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; - -/** 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.util/src/org/argeo/osgi/useradmin/TokenUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java deleted file mode 100644 index 178b4ae82..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 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.util/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java deleted file mode 100644 index 05ed7cf7c..000000000 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.osgi.useradmin; - -import org.argeo.util.directory.Directory; -import org.argeo.util.directory.HierarchyUnit; -import org.osgi.service.useradmin.Role; - -/** Information about a user directory. */ -public interface UserDirectory extends Directory { - - 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.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 5728b90db..000000000 --- a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java +++ /dev/null @@ -1,56 +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.Executor; -import java.util.concurrent.ForkJoinPool; - -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; - -public class OsgiRegister { - 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(); - } - - public void 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/CompositeString.java b/org.argeo.util/src/org/argeo/util/CompositeString.java deleted file mode 100644 index 2f8587dec..000000000 --- a/org.argeo.util/src/org/argeo/util/CompositeString.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.argeo.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.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/CurrentSubject.java b/org.argeo.util/src/org/argeo/util/CurrentSubject.java deleted file mode 100644 index 60ce3cf26..000000000 --- a/org.argeo.util/src/org/argeo/util/CurrentSubject.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.argeo.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.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/ExceptionsChain.java b/org.argeo.util/src/org/argeo/util/ExceptionsChain.java deleted file mode 100644 index 9f824213d..000000000 --- a/org.argeo.util/src/org/argeo/util/ExceptionsChain.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.argeo.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.util/src/org/argeo/util/FsUtils.java b/org.argeo.util/src/org/argeo/util/FsUtils.java deleted file mode 100644 index cd61b5619..000000000 --- a/org.argeo.util/src/org/argeo/util/FsUtils.java +++ /dev/null @@ -1,78 +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. */ - 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.util/src/org/argeo/util/LangUtils.java b/org.argeo.util/src/org/argeo/util/LangUtils.java deleted file mode 100644 index 1aee28c03..000000000 --- a/org.argeo.util/src/org/argeo/util/LangUtils.java +++ /dev/null @@ -1,331 +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.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.util/src/org/argeo/util/OS.java b/org.argeo.util/src/org/argeo/util/OS.java deleted file mode 100644 index 174f45b78..000000000 --- a/org.argeo.util/src/org/argeo/util/OS.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.argeo.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.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 30404f1e4..000000000 --- a/org.argeo.util/src/org/argeo/util/StreamUtils.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.argeo.util; - -import java.io.BufferedReader; -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 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.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/directory/Directory.java b/org.argeo.util/src/org/argeo/util/directory/Directory.java deleted file mode 100644 index 988658969..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/Directory.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.util.directory; - -import java.util.Optional; - -import org.argeo.util.transaction.WorkControl; - -/** An information directory (typicylly LDAP). */ -public interface Directory 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.util/src/org/argeo/util/directory/DirectoryConf.java b/org.argeo.util/src/org/argeo/util/directory/DirectoryConf.java deleted file mode 100644 index 4450ca474..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/DirectoryConf.java +++ /dev/null @@ -1,246 +0,0 @@ -package org.argeo.util.directory; - -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.util.directory.ldap.IpaUtils; -import org.argeo.util.naming.NamingUtils; - -/** 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.util/src/org/argeo/util/directory/DirectoryDigestUtils.java b/org.argeo.util/src/org/argeo/util/directory/DirectoryDigestUtils.java deleted file mode 100644 index d07d2d2ed..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/DirectoryDigestUtils.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/directory/HierarchyUnit.java b/org.argeo.util/src/org/argeo/util/directory/HierarchyUnit.java deleted file mode 100644 index 947b6bc85..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/HierarchyUnit.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.util.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 Directory}. - */ - 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.) - */ - boolean isFunctional(); - - /** - * The base of this organisational unit within the hierarchy. This would - * typically be an LDAP base DN. - */ - String getBase(); - - /** The related {@link Directory}. */ - Directory getDirectory(); - - /** Its metadata (typically LDAP attributes). */ - Dictionary getProperties(); -} diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java deleted file mode 100644 index 28d8d081c..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java +++ /dev/null @@ -1,560 +0,0 @@ -package org.argeo.util.directory.ldap; - -import static org.argeo.util.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.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.osgi.useradmin.OsUserDirectory; -import org.argeo.util.directory.Directory; -import org.argeo.util.directory.DirectoryConf; -import org.argeo.util.directory.HierarchyUnit; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.argeo.util.transaction.WorkControl; -import org.argeo.util.transaction.WorkingCopyXaResource; -import org.argeo.util.transaction.XAResourceProvider; - -/** A {@link Directory} based either on LDAP or LDIF. */ -public abstract class AbstractLdapDirectory implements Directory, 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[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() }); - - private WorkControl transactionControl; - private WorkingCopyXaResource xaResource; - - private LdapDirectoryDao directoryDao; - - 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); - } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) { - directoryDao = new LdifDao(this); - } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) { - directoryDao = new OsUserDirectory(this); - // 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(LdapAttrs.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 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 isFunctional() { - return true; - } - - @Override - public Directory 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(LdapAttrs.objectClass.name(), LdapObjs.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 = path.startsWith("accounts/")/* IPA */ ? LdapAttrs.cn.name() : LdapAttrs.ou.name(); - if (parentRdn != null) { - if (getUserBaseRdn().equals(parentRdn)) - attr = LdapAttrs.uid.name(); - else if (getGroupBaseRdn().equals(parentRdn)) - attr = LdapAttrs.cn.name(); - else if (getSystemRoleBaseRdn().equals(parentRdn)) - attr = LdapAttrs.cn.name(); - } - Rdn rdn = new Rdn(attr, segment); - name.add(rdn); - parentRdn = rdn; - } - return name; - } catch (InvalidNameException e) { - throw new IllegalStateException("Cannot get role " + path, e); - } - - } - - /* - * UTILITIES - */ - protected boolean isExternal(LdapName name) { - return !name.startsWith(baseDn); - } - - protected static boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) { - return hasObjectClass(attrs, objectClass.name()); - } - - protected static boolean hasObjectClass(Attributes attrs, String objectClass) { - try { - Attribute attr = attrs.get(LdapAttrs.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 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.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java deleted file mode 100644 index 8e132887d..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.util.directory.ldap; - -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; - -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.util/src/org/argeo/util/directory/ldap/AttributesDictionary.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AttributesDictionary.java deleted file mode 100644 index 7b0095fbe..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AttributesDictionary.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/directory/ldap/AuthPassword.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AuthPassword.java deleted file mode 100644 index e10f45756..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AuthPassword.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.argeo.util.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.util.naming.LdapAttrs; - -/** 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 IllegalStateException("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/directory/ldap/DefaultLdapEntry.java b/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java deleted file mode 100644 index 6b6154dcc..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java +++ /dev/null @@ -1,460 +0,0 @@ -package org.argeo.util.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.util.directory.DirectoryDigestUtils; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; - -/** An entry in an LDAP (or LDIF) directory. */ -public class DefaultLdapEntry implements LdapEntry { - private final AbstractLdapDirectory directory; - - private final LdapName dn; - -// private Attributes publishedAttributes; - - // Temporarily expose the fields - private AttributeDictionary properties; - private AttributeDictionary credentials; - - protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn) { - Objects.requireNonNull(directory); - Objects.requireNonNull(dn); - this.directory = directory; - this.dn = dn; -// this.publishedAttributes = attributes; -// properties = new AttributeDictionary(false); -// credentials = new AttributeDictionary(true); - } - - @Override - public LdapName getDn() { - return dn; - } - - public synchronized Attributes getAttributes() { -// // lazy loading -// if (publishedAttributes == null) -// publishedAttributes = getDirectory().getDirectoryDao().doGetAttributes(dn); - 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(LdapAttrs.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(LdapAttrs.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(LdapAttrs.objectClasses.name()) ? getAttributes().get(key.toString()) - : getAttributes().get(LdapAttrs.objectClass.name()); - 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; - // special case for object class - if (key.equals(LdapAttrs.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(LdapObjs.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(LdapAttrs.userPassword.name(), hashedPassword); - } - if (key.startsWith("X-")) { - return put(LdapAttrs.authPassword.name(), value); - } - - // start editing - getDirectory().checkEdit(); - if (!isEditing()) - startEditing(); - - // object classes special case. - if (key.equals(LdapAttrs.objectClasses.name())) { - Attribute attribute = new BasicAttribute(LdapAttrs.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.util/src/org/argeo/util/directory/ldap/IpaUtils.java b/org.argeo.util/src/org/argeo/util/directory/ldap/IpaUtils.java deleted file mode 100644 index 68b40868a..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/IpaUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -package org.argeo.util.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 javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.util.directory.DirectoryConf; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.dns.DnsBrowser; - -/** 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,cn=accounts"; - - public final static Rdn IPA_ACCOUNTS_RDN; - static { - try { - IPA_ACCOUNTS_RDN = new Rdn(LdapAttrs.cn.name(), "accounts"); - } catch (InvalidNameException e) { - // should not happen - throw new IllegalStateException(e); - } - } - - private final static String KRB_PRINCIPAL_NAME = LdapAttrs.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("\\."); - 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 + "," + IPA_ACCOUNTS_RDN + "," + 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 (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.util/src/org/argeo/util/directory/ldap/LdapConnection.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapConnection.java deleted file mode 100644 index 748efe350..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapConnection.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.argeo.util.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.util.naming.LdapAttrs; -import org.argeo.util.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", 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 (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.util/src/org/argeo/util/directory/ldap/LdapDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java deleted file mode 100644 index 0f6e324ad..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java +++ /dev/null @@ -1,263 +0,0 @@ -package org.argeo.util.directory.ldap; - -import static org.argeo.util.naming.LdapAttrs.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.util.directory.HierarchyUnit; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; - -/** 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(LdapAttrs.objectClass.name(), LdapObjs.top.name()); - attrs.put(LdapAttrs.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(LdapAttrs.objectClass.name(), LdapObjs.top.name()); - attrs.put(LdapAttrs.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(LdapAttrs.objectClass.name(), LdapObjs.top.name()); - attrs.put(LdapAttrs.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 searchFilter = "(|(" + objectClass + "=" + LdapObjs.organizationalUnit.name() + ")(" + objectClass - + "=" + LdapObjs.organization.name() + "))"; -// String searchFilter = "(|(" + objectClass + "=" + LdapObjs.organizationalUnit.name() + ")(" + objectClass -// + "=" + LdapObjs.organization.name() + ")(cn=accounts)(cn=users)(cn=groups))"; - - 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.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java deleted file mode 100644 index f31780011..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.util.directory.ldap; - -import java.util.List; - -import javax.naming.NameNotFoundException; -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; - -import org.argeo.util.directory.HierarchyUnit; -import org.argeo.util.transaction.WorkingCopyProcessor; - -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.util/src/org/argeo/util/directory/ldap/LdapEntry.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapEntry.java deleted file mode 100644 index 4657c8798..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapEntry.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.argeo.util.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.util.naming.LdapAttrs; - -/** 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(LdapAttrs.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(LdapAttrs.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.util/src/org/argeo/util/directory/ldap/LdapEntryWorkingCopy.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapEntryWorkingCopy.java deleted file mode 100644 index 381c11b2f..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapEntryWorkingCopy.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.util.directory.ldap; - -import javax.naming.directory.Attributes; -import javax.naming.ldap.LdapName; - -import org.argeo.util.transaction.AbstractWorkingCopy; - -/** Working copy for a user directory being edited. */ -public class LdapEntryWorkingCopy extends AbstractWorkingCopy { - @Override - protected LdapName getId(LdapEntry entry) { - return entry.getDn(); - } - - @Override - protected Attributes cloneAttributes(LdapEntry entry) { - return (Attributes) entry.getAttributes().clone(); - } -} diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java deleted file mode 100644 index 961f2e358..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.argeo.util.directory.ldap; - -import java.util.Locale; - -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.util.directory.HierarchyUnit; - -/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */ -public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit { - private final boolean functional; - - public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn) { - super(directory, dn); - - Rdn rdn = LdapNameUtils.getLastRdn(dn); - 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 boolean isFunctional() { - return functional; - } - - @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.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java deleted file mode 100644 index 88d317542..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/directory/ldap/LdifDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java deleted file mode 100644 index c200faa27..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java +++ /dev/null @@ -1,306 +0,0 @@ -package org.argeo.util.directory.ldap; - -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.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.util.directory.HierarchyUnit; -import org.argeo.util.naming.LdapObjs; -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(LdapObjs.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.util/src/org/argeo/util/directory/ldap/LdifParser.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdifParser.java deleted file mode 100644 index 0022943e1..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdifParser.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.argeo.util.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.util.naming.LdapAttrs; - -/** 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(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/directory/ldap/LdifWriter.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdifWriter.java deleted file mode 100644 index a10f16938..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdifWriter.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.argeo.util.directory.ldap; - -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; - -/** 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.util/src/org/argeo/util/directory/ldap/SharedSecret.java b/org.argeo.util/src/org/argeo/util/directory/ldap/SharedSecret.java deleted file mode 100644 index eaab167e8..000000000 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/SharedSecret.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.util.directory.ldap; - -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; - -import org.argeo.util.naming.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.util/src/org/argeo/util/http/HttpHeader.java b/org.argeo.util/src/org/argeo/util/http/HttpHeader.java deleted file mode 100644 index 74cf94c03..000000000 --- a/org.argeo.util/src/org/argeo/util/http/HttpHeader.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.util.http; - -/** Standard HTTP headers (including WebDav). */ -public enum HttpHeader { - AUTHORIZATION("Authorization"), // - WWW_AUTHENTICATE("WWW-Authenticate"), // - ALLOW("Allow"), // - - // WebDav - DAV("DAV"), // - DEPTH("Depth"), // - ; - - 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.util/src/org/argeo/util/http/HttpMethod.java b/org.argeo.util/src/org/argeo/util/http/HttpMethod.java deleted file mode 100644 index 27b4d8f19..000000000 --- a/org.argeo.util/src/org/argeo/util/http/HttpMethod.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.util.http; - -/** Generic HTTP methods. */ -public enum HttpMethod { - OPTIONS, // - HEAD, // - GET, // - POST, // - PUT, // - DELETE, // - - // WebDav - PROPFIND, // - PROPPATCH, // - MKCOL, // - MOVE, // - COPY, // - ; -} diff --git a/org.argeo.util/src/org/argeo/util/http/HttpServerUtils.java b/org.argeo.util/src/org/argeo/util/http/HttpServerUtils.java deleted file mode 100644 index 9127d2c21..000000000 --- a/org.argeo.util/src/org/argeo/util/http/HttpServerUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.argeo.util.http; - -import java.net.URI; -import java.util.Objects; - -import com.sun.net.httpserver.HttpContext; -import com.sun.net.httpserver.HttpExchange; - -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.util/src/org/argeo/util/http/HttpStatus.java b/org.argeo.util/src/org/argeo/util/http/HttpStatus.java deleted file mode 100644 index 11e0a3645..000000000 --- a/org.argeo.util/src/org/argeo/util/http/HttpStatus.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/internal/DisplayQName.java b/org.argeo.util/src/org/argeo/util/internal/DisplayQName.java deleted file mode 100644 index 6cc39dc6a..000000000 --- a/org.argeo.util/src/org/argeo/util/internal/DisplayQName.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.util.internal; - -import javax.xml.namespace.QName; - -public class DisplayQName extends QName { - private static final long serialVersionUID = 2376484886212253123L; - - public DisplayQName(String namespaceURI, String localPart, String prefix) { - super(namespaceURI, localPart, prefix); - } - - public DisplayQName(String localPart) { - super(localPart); - } - - @Override - public String toString() { - String prefix = getPrefix(); - assert prefix != null; - return "".equals(prefix) ? getLocalPart() : prefix + ":" + getLocalPart(); - } - - } \ No newline at end of file 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 e339edeef..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/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 1a6642f05..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java +++ /dev/null @@ -1,356 +0,0 @@ -package org.argeo.util.naming; - -import java.util.function.Supplier; - -import javax.xml.namespace.QName; - -import org.argeo.util.internal.DisplayQName; - -/** - * 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"), - - // 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 static String LDAP_ = "ldap:"; - - private final String oid, spec; - private final QName value; - - LdapAttrs(String oid, String spec) { - this.oid = oid; - this.spec = spec; - this.value = new DisplayQName(LdapObjs.LDAP_NAMESPACE_URI, name(), LdapObjs.LDAP_DEFAULT_PREFIX); - } - - 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(); - } - - @Override - public String get() { - return LdapObjs.LDAP_DEFAULT_PREFIX + ":" + name(); - } - - @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 995c68cc9..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.argeo.util.naming; - -import java.util.function.Supplier; - -import javax.xml.namespace.QName; - -import org.argeo.util.internal.DisplayQName; - -/** - * Standard LDAP object classes as per - * https://www.ldap.com/ldap- - * oid-reference - */ -public enum LdapObjs implements SpecifiedName, Supplier { - 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"), - - // - ; - - /** MUST be equal to ContentRepository LDAP namespace. */ - final static String LDAP_NAMESPACE_URI = "http://www.argeo.org/ns/ldap"; - /** MUST be equal to ContentRepository LDAP prefix. */ - final static String LDAP_DEFAULT_PREFIX = "ldap"; - - private final String oid, spec; - private final QName value; - - private LdapObjs(String oid, String spec) { - this.oid = oid; - this.spec = spec; - this.value = new DisplayQName(LDAP_NAMESPACE_URI, name(), LDAP_DEFAULT_PREFIX); - } - - public QName qName() { - return value; - } - - public String getOid() { - return oid; - } - - public String getSpec() { - return spec; - } - - @Deprecated - public String property() { - return get(); - } - - @Override - public String get() { - return LdapObjs.LDAP_DEFAULT_PREFIX + ":" + name(); - } - -} 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/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/dns/DnsBrowser.java b/org.argeo.util/src/org/argeo/util/naming/dns/DnsBrowser.java deleted file mode 100644 index 4fba43405..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/dns/DnsBrowser.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.argeo.util.naming.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.util/src/org/argeo/util/naming/dns/SrvRecord.java b/org.argeo.util/src/org/argeo/util/naming/dns/SrvRecord.java deleted file mode 100644 index ea6f3cc96..000000000 --- a/org.argeo.util/src/org/argeo/util/naming/dns/SrvRecord.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.argeo.util.naming.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.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 275811e9d..000000000 --- a/org.argeo.util/src/org/argeo/util/register/Component.java +++ /dev/null @@ -1,333 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/register/ComponentRegister.java b/org.argeo.util/src/org/argeo/util/register/ComponentRegister.java deleted file mode 100644 index d78b6badb..000000000 --- a/org.argeo.util/src/org/argeo/util/register/ComponentRegister.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/register/RankingKey.java b/org.argeo.util/src/org/argeo/util/register/RankingKey.java deleted file mode 100644 index 7a43e359e..000000000 --- a/org.argeo.util/src/org/argeo/util/register/RankingKey.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/register/SimpleRegister.java b/org.argeo.util/src/org/argeo/util/register/SimpleRegister.java deleted file mode 100644 index 7aa9ebef9..000000000 --- a/org.argeo.util/src/org/argeo/util/register/SimpleRegister.java +++ /dev/null @@ -1,124 +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.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.util/src/org/argeo/util/transaction/AbstractWorkingCopy.java b/org.argeo.util/src/org/argeo/util/transaction/AbstractWorkingCopy.java deleted file mode 100644 index 0da35ac7b..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/AbstractWorkingCopy.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/transaction/JtaStatusAdapter.java b/org.argeo.util/src/org/argeo/util/transaction/JtaStatusAdapter.java deleted file mode 100644 index bd977069f..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/JtaStatusAdapter.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.argeo.util.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/util/transaction/SimpleRollbackException.java b/org.argeo.util/src/org/argeo/util/transaction/SimpleRollbackException.java deleted file mode 100644 index 010b549c7..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/SimpleRollbackException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.util.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/util/transaction/SimpleTransaction.java b/org.argeo.util/src/org/argeo/util/transaction/SimpleTransaction.java deleted file mode 100644 index 56ef06353..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/SimpleTransaction.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.argeo.util.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/util/transaction/SimpleTransactionManager.java b/org.argeo.util/src/org/argeo/util/transaction/SimpleTransactionManager.java deleted file mode 100644 index f5be7c8e5..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/SimpleTransactionManager.java +++ /dev/null @@ -1,214 +0,0 @@ -package org.argeo.util.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/util/transaction/TransactionStatusAdapter.java b/org.argeo.util/src/org/argeo/util/transaction/TransactionStatusAdapter.java deleted file mode 100644 index a74fef1c9..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/TransactionStatusAdapter.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.util.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/util/transaction/UuidXid.java b/org.argeo.util/src/org/argeo/util/transaction/UuidXid.java deleted file mode 100644 index b6acebec1..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/UuidXid.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.argeo.util.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/util/transaction/WorkContext.java b/org.argeo.util/src/org/argeo/util/transaction/WorkContext.java deleted file mode 100644 index e818b830f..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkContext.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.util.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/util/transaction/WorkControl.java b/org.argeo.util/src/org/argeo/util/transaction/WorkControl.java deleted file mode 100644 index db0e475a3..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkControl.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.util.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/util/transaction/WorkTransaction.java b/org.argeo.util/src/org/argeo/util/transaction/WorkTransaction.java deleted file mode 100644 index 245ca41af..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkTransaction.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.util.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/util/transaction/WorkingCopy.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopy.java deleted file mode 100644 index 9dd3fc537..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopy.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/transaction/WorkingCopyProcessor.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyProcessor.java deleted file mode 100644 index cdd640488..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyProcessor.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.util.transaction; - -public interface WorkingCopyProcessor> { - void prepare(WC wc); - - void commit(WC wc); - - void rollback(WC wc); - - WC newWorkingCopy(); -} diff --git a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java deleted file mode 100644 index ddb605a19..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.argeo.util.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.util/src/org/argeo/util/transaction/XAResourceProvider.java b/org.argeo.util/src/org/argeo/util/transaction/XAResourceProvider.java deleted file mode 100644 index b0b211bad..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/XAResourceProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.util.transaction; - -import javax.transaction.xa.XAResource; - -public interface XAResourceProvider { - XAResource getXaResource(); -} diff --git a/org.argeo.util/src/org/argeo/util/transaction/package-info.java b/org.argeo.util/src/org/argeo/util/transaction/package-info.java deleted file mode 100644 index f4811613d..000000000 --- a/org.argeo.util/src/org/argeo/util/transaction/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Minimalistic and partial XA transaction manager implementation. */ -package org.argeo.util.transaction; \ No newline at end of file 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/sdk/argeo-build b/sdk/argeo-build index 639ffc61c..5bf388711 160000 --- a/sdk/argeo-build +++ b/sdk/argeo-build @@ -1 +1 @@ -Subproject commit 639ffc61c03f6aebf9cfe5e0f79dd0c4c6aa632a +Subproject commit 5bf388711048a659992c292c4b9bdcbc595e066b diff --git a/sdk/branches/unstable.bnd b/sdk/branches/unstable.bnd index 4146cb099..98d75162f 100644 --- a/sdk/branches/unstable.bnd +++ b/sdk/branches/unstable.bnd @@ -1,4 +1,4 @@ major=2 minor=3 -micro=9 +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 efae9ba73..1ca557b7e 100644 --- a/sdk/cms-e4-rap.properties +++ b/sdk/cms-e4-rap.properties @@ -13,11 +13,6 @@ org.argeo.cms.lib.sshd,\ org.argeo.cms.lib.equinox,\ org.argeo.cms.lib.jetty,\ -argeo.osgi.start.4=\ -org.argeo.cms.jcr - -argeo.osgi.start.5.node=\ -org.argeo.cms.e4.rap # Local argeo.node.repo.type=h2 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.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 8b7f969ec..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,31 +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.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 \ +-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 105f76962..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/osgi/equinox/org.argeo.tp.osgi/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/swt/org.argeo.cms.e4/META-INF/.gitignore b/swt/org.argeo.cms.e4/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/swt/org.argeo.cms.e4/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/swt/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/swt/org.argeo.cms.e4/OSGI-INF/homeRepository.xml deleted file mode 100644 index 2722aabb6..000000000 --- a/swt/org.argeo.cms.e4/OSGI-INF/homeRepository.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/swt/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml b/swt/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml deleted file mode 100644 index cc7087b6e..000000000 --- a/swt/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/swt/org.argeo.cms.e4/bnd.bnd b/swt/org.argeo.cms.e4/bnd.bnd index 7cf086181..8839805c1 100644 --- a/swt/org.argeo.cms.e4/bnd.bnd +++ b/swt/org.argeo.cms.e4/bnd.bnd @@ -1,19 +1,20 @@ -Service-Component: OSGI-INF/homeRepository.xml,\ -OSGI-INF/userAdminWrapper.xml,\ -OSGI-INF/defaultCallbackHandler.xml +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,\ -org.eclipse.e4.ui.model.application,\ +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/e4xmi/cms-devops.e4xmi b/swt/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi deleted file mode 100644 index f71b83e1f..000000000 --- a/swt/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi +++ /dev/null @@ -1,127 +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/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 index 37f1c0233..66a5ec8c7 100644 --- 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 @@ -8,7 +8,7 @@ import javax.security.auth.Subject; import javax.servlet.http.HttpServletRequest; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.CurrentUser; +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; diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java deleted file mode 100644 index aabfbf5dd..000000000 --- a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java +++ /dev/null @@ -1,47 +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.eclipse.ui.fs.SimpleFsBrowser; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; - -/** Browse the node file system. */ -public class NodeFsBrowserView { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".nodeFsBrowserView"; - - @Inject - FileSystemProvider nodeFileSystemProvider; - - @PostConstruct - public void createPartControl(Composite parent) { - try { - // URI uri = new URI("node://root:demo@localhost:7070/"); - URI uri = new URI("node:///"); - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) - fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); - Path nodePath = fileSystem.getPath("/"); - - Path localPath = Paths.get(System.getProperty("user.home")); - - SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS); - browser.setInput(nodePath, localPath); -// AdvancedFsBrowser browser = new AdvancedFsBrowser(); -// browser.createUi(parent, localPath); - } catch (Exception e) { - throw new RuntimeException("Cannot open file system browser", e); - } - } - - public void setFocus() { - } -} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java deleted file mode 100644 index b481dd48a..000000000 --- a/swt/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/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 index 7ef8c59da..9624c2d70 100644 --- 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 @@ -12,15 +12,14 @@ import javax.inject.Inject; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.swt.CmsException; +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.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.util.transaction.WorkTransaction; +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.jface.dialogs.Dialog; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -46,7 +45,7 @@ public class ChangePassword { @Execute public void execute() { ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin); - if (dialog.open() == Dialog.OK) { + if (dialog.open() == CmsDialog.OK) { new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(), CmsMessageDialog.INFORMATION).open(); } @@ -58,13 +57,13 @@ public class ChangePassword { try { dn = new LdapName(name); } catch (InvalidNameException e) { - throw new CmsException("Invalid user dn " + name, e); + throw new IllegalArgumentException("Invalid user dn " + name, e); } User user = (User) userAdmin.getRole(dn.toString()); if (!user.hasCredential(null, oldPassword)) - throw new CmsException("Invalid password"); + throw new IllegalArgumentException("Invalid password"); if (Arrays.equals(newPassword, new char[0])) - throw new CmsException("New password empty"); + throw new IllegalArgumentException("New password empty"); try { userTransaction.begin(); user.getCredentials().put(null, newPassword); @@ -82,7 +81,7 @@ public class ChangePassword { if (e instanceof RuntimeException) throw (RuntimeException) e; else - throw new CmsException("Cannot change password", e); + throw new IllegalStateException("Cannot change password", e); } } @@ -116,11 +115,11 @@ public class ChangePassword { protected void okPressed() { try { if (!newPassword1.getText().equals(newPassword2.getText())) - throw new CmsException("New passwords are different"); + throw new IllegalArgumentException("New passwords are different"); changePassword(oldPassword.getTextChars(), newPassword1.getTextChars()); - closeShell(OK); + closeShell(CmsDialog.OK); } catch (Exception e) { - ErrorFeedback.show("Cannot change password", e); + CmsFeedback.error("Cannot change password", e); } } 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 index c2ae4bff7..cce18020d 100644 --- 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 @@ -1,11 +1,9 @@ package org.argeo.cms.e4.handlers; -import java.security.AccessController; - import javax.security.auth.Subject; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.swt.CmsException; +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; @@ -17,11 +15,11 @@ public class CloseWorkbench { } protected void logout() { - Subject subject = Subject.getSubject(AccessController.getContext()); + Subject subject = CurrentSubject.current(); try { CurrentUser.logoutCmsSession(subject); } catch (Exception e) { - throw new CmsException("Cannot log out", e); + throw new IllegalStateException("Cannot log out", e); } } diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java deleted file mode 100644 index e9536830f..000000000 --- a/swt/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.cms.ux.widgets.TreeParent; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; - -/** A tree element representing a {@link Bundle} */ -class BundleNode extends TreeParent { - private final Bundle bundle; - - public BundleNode(Bundle bundle) { - this(bundle, false); - } - - @SuppressWarnings("rawtypes") - public BundleNode(Bundle bundle, boolean hasChildren) { - super(bundle.getSymbolicName()); - this.bundle = bundle; - - if (hasChildren) { - // REFERENCES - ServiceReference[] usedServices = bundle.getServicesInUse(); - if (usedServices != null) { - for (ServiceReference sr : usedServices) { - if (sr != null) - addChild(new ServiceReferenceNode(sr, false)); - } - } - - // SERVICES - ServiceReference[] registeredServices = bundle - .getRegisteredServices(); - if (registeredServices != null) { - for (ServiceReference sr : registeredServices) { - if (sr != null) - addChild(new ServiceReferenceNode(sr, true)); - } - } - } - - } - - Bundle getBundle() { - return bundle; - } -} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java deleted file mode 100644 index c639255b6..000000000 --- a/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java deleted file mode 100644 index 95b1eb2cb..000000000 --- a/swt/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 org.argeo.api.cms.CmsSession; -import org.argeo.cms.auth.RoleNameUtils; -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) { - String userDn = ((CmsSession) element).getUserDn(); - return RoleNameUtils.getLastRdnValue(userDn); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getUserDn().toString(); - } - }); - new ColumnViewerComparator(column); - - // UUID - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("UUID"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getUuid().toString(); - } - - public String getToolTipText(Object element) { - return getText(element); - } - }); - new ColumnViewerComparator(column); - - // Local ID - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Local ID"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getLocalId(); - } - - public String getToolTipText(Object element) { - return getText(element); - } - }); - new ColumnViewerComparator(column); - - viewer.setInput(bc); - - } - - @Focus - public void setFocus() { - if (viewer != null) - viewer.getControl().setFocus(); - } - - /** Content provider managing the array of bundles */ - private static class CmsSessionContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -8533792785725875977L; - - public Object[] getElements(Object inputElement) { - if (inputElement instanceof BundleContext) { - BundleContext bc = (BundleContext) inputElement; - Collection> 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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java deleted file mode 100644 index 6317882c4..000000000 --- a/swt/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.cms.ux.widgets.TreeParent; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** The OSGi runtime from a module perspective. */ -public class ModulesView { - private final static BundleContext bc = FrameworkUtil.getBundle(ModulesView.class).getBundleContext(); - private TreeViewer viewer; - - @PostConstruct - public void createPartControl(Composite parent) { - viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); - viewer.setContentProvider(new ModulesContentProvider()); - viewer.setLabelProvider(new ModulesLabelProvider()); - viewer.setInput(bc); - } - - @Focus - public void setFocus() { - viewer.getTree().setFocus(); - } - - private class ModulesContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = 3819934804640641721L; - - public Object[] getElements(Object inputElement) { - return getChildren(inputElement); - } - - public Object[] getChildren(Object parentElement) { - if (parentElement instanceof BundleContext) { - BundleContext bundleContext = (BundleContext) parentElement; - Bundle[] bundles = bundleContext.getBundles(); - - List 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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java deleted file mode 100644 index 5db8bd151..000000000 --- a/swt/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.swt.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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java deleted file mode 100644 index 7217fe612..000000000 --- a/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java deleted file mode 100644 index 1c60811d2..000000000 --- a/swt/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.cms.ux.widgets.TreeParent; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; - -/** A tree element representing a {@link ServiceReference} */ -@SuppressWarnings({ "rawtypes" }) -class ServiceReferenceNode extends TreeParent { - private final ServiceReference serviceReference; - private final boolean published; - - public ServiceReferenceNode(ServiceReference serviceReference, - boolean published) { - super(serviceReference.toString()); - this.serviceReference = serviceReference; - this.published = published; - - if (isPublished()) { - Bundle[] usedBundles = serviceReference.getUsingBundles(); - if (usedBundles != null) { - for (Bundle b : usedBundles) { - if (b != null) - addChild(new BundleNode(b)); - } - } - } else { - Bundle provider = serviceReference.getBundle(); - addChild(new BundleNode(provider)); - } - - for (String key : serviceReference.getPropertyKeys()) { - addChild(new TreeParent(key + "=" - + serviceReference.getProperty(key))); - } - - } - - public ServiceReference getServiceReference() { - return serviceReference; - } - - public boolean isPublished() { - return published; - } -} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java deleted file mode 100644 index 5cb5b6563..000000000 --- a/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java deleted file mode 100644 index 873bf3118..000000000 --- a/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java deleted file mode 100644 index f2a73f210..000000000 --- a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.cms.e4.parts; - -import java.time.ZonedDateTime; - -import javax.annotation.PostConstruct; - -import org.argeo.api.cms.CmsSession; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; - -/** A canonical view of the logged in user. */ -public class EgoDashboard { -// private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext(); - - @PostConstruct - public void createPartControl(Composite p) { - p.setLayout(new GridLayout()); - String username = CurrentUser.getUsername(); - - CmsSwtUtils.lbl(p, "" + 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 = CurrentUser.getCmsSession(); - ZonedDateTime loggedIndSince = cmsSession.getCreationTime(); - CmsSwtUtils.lbl(p, "Session:"); - CmsSwtUtils.txt(p, cmsSession.getUuid().toString()); - CmsSwtUtils.lbl(p, "Logged in since:"); - CmsSwtUtils.txt(p, loggedIndSince.toString()); -// } - } -} diff --git a/swt/org.argeo.cms.swt/bnd.bnd b/swt/org.argeo.cms.swt/bnd.bnd index 2dda08b2a..1dfa6599d 100644 --- a/swt/org.argeo.cms.swt/bnd.bnd +++ b/swt/org.argeo.cms.swt/bnd.bnd @@ -1,5 +1,6 @@ Import-Package: org.eclipse.swt,\ org.eclipse.jface.window,\ +org.eclipse.jface.dialogs,\ org.eclipse.core.commands.common,\ javax.servlet.*;version="[3,5)",\ * diff --git a/swt/org.argeo.cms.swt/icons/actions/add.png b/swt/org.argeo.cms.swt/icons/actions/add.png deleted file mode 100644 index 5c06bf082..000000000 Binary files a/swt/org.argeo.cms.swt/icons/actions/add.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/actions/close-all.png b/swt/org.argeo.cms.swt/icons/actions/close-all.png deleted file mode 100644 index 81bfc950b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/actions/close-all.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/actions/delete.png b/swt/org.argeo.cms.swt/icons/actions/delete.png deleted file mode 100644 index 9712723d7..000000000 Binary files a/swt/org.argeo.cms.swt/icons/actions/delete.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/actions/edit.png b/swt/org.argeo.cms.swt/icons/actions/edit.png deleted file mode 100644 index ad3db9f42..000000000 Binary files a/swt/org.argeo.cms.swt/icons/actions/edit.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/actions/save-all.png b/swt/org.argeo.cms.swt/icons/actions/save-all.png deleted file mode 100644 index f48ed320b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/actions/save-all.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/actions/save.png b/swt/org.argeo.cms.swt/icons/actions/save.png deleted file mode 100644 index 1c58ada49..000000000 Binary files a/swt/org.argeo.cms.swt/icons/actions/save.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/active.gif b/swt/org.argeo.cms.swt/icons/active.gif deleted file mode 100644 index 7d24707ee..000000000 Binary files a/swt/org.argeo.cms.swt/icons/active.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/add.gif b/swt/org.argeo.cms.swt/icons/add.gif deleted file mode 100644 index 252d7ebcb..000000000 Binary files a/swt/org.argeo.cms.swt/icons/add.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/add.png b/swt/org.argeo.cms.swt/icons/add.png deleted file mode 100644 index c7edfecaa..000000000 Binary files a/swt/org.argeo.cms.swt/icons/add.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/addFolder.gif b/swt/org.argeo.cms.swt/icons/addFolder.gif deleted file mode 100644 index d3f43d977..000000000 Binary files a/swt/org.argeo.cms.swt/icons/addFolder.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/addPrivileges.gif b/swt/org.argeo.cms.swt/icons/addPrivileges.gif deleted file mode 100644 index a6b251fc8..000000000 Binary files a/swt/org.argeo.cms.swt/icons/addPrivileges.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/addRepo.gif b/swt/org.argeo.cms.swt/icons/addRepo.gif deleted file mode 100644 index 26d81c065..000000000 Binary files a/swt/org.argeo.cms.swt/icons/addRepo.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/addWorkspace.png b/swt/org.argeo.cms.swt/icons/addWorkspace.png deleted file mode 100644 index bbee7755f..000000000 Binary files a/swt/org.argeo.cms.swt/icons/addWorkspace.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/adminLog.gif b/swt/org.argeo.cms.swt/icons/adminLog.gif deleted file mode 100644 index 6ef3bca66..000000000 Binary files a/swt/org.argeo.cms.swt/icons/adminLog.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/batch.gif b/swt/org.argeo.cms.swt/icons/batch.gif deleted file mode 100644 index b8ca14a8b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/batch.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/begin.gif b/swt/org.argeo.cms.swt/icons/begin.gif deleted file mode 100755 index feb8e94a7..000000000 Binary files a/swt/org.argeo.cms.swt/icons/begin.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/binary.png b/swt/org.argeo.cms.swt/icons/binary.png deleted file mode 100644 index fdf4f82be..000000000 Binary files a/swt/org.argeo.cms.swt/icons/binary.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/browser.gif b/swt/org.argeo.cms.swt/icons/browser.gif deleted file mode 100644 index 6c7320c69..000000000 Binary files a/swt/org.argeo.cms.swt/icons/browser.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/bundles.gif b/swt/org.argeo.cms.swt/icons/bundles.gif deleted file mode 100644 index e9a6bd966..000000000 Binary files a/swt/org.argeo.cms.swt/icons/bundles.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/changePassword.gif b/swt/org.argeo.cms.swt/icons/changePassword.gif deleted file mode 100644 index 274a850e4..000000000 Binary files a/swt/org.argeo.cms.swt/icons/changePassword.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/clear.gif b/swt/org.argeo.cms.swt/icons/clear.gif deleted file mode 100644 index 6bc10f9d0..000000000 Binary files a/swt/org.argeo.cms.swt/icons/clear.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/close-all.png b/swt/org.argeo.cms.swt/icons/close-all.png deleted file mode 100644 index 85d4d429b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/close-all.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/commit.gif b/swt/org.argeo.cms.swt/icons/commit.gif deleted file mode 100755 index 876f3eb16..000000000 Binary files a/swt/org.argeo.cms.swt/icons/commit.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/delete.png b/swt/org.argeo.cms.swt/icons/delete.png deleted file mode 100644 index 676a39dcf..000000000 Binary files a/swt/org.argeo.cms.swt/icons/delete.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/dumpNode.gif b/swt/org.argeo.cms.swt/icons/dumpNode.gif deleted file mode 100644 index 14eb1be09..000000000 Binary files a/swt/org.argeo.cms.swt/icons/dumpNode.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/file.gif b/swt/org.argeo.cms.swt/icons/file.gif deleted file mode 100644 index ef3028807..000000000 Binary files a/swt/org.argeo.cms.swt/icons/file.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/folder.gif b/swt/org.argeo.cms.swt/icons/folder.gif deleted file mode 100644 index 42e027c93..000000000 Binary files a/swt/org.argeo.cms.swt/icons/folder.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/getSize.gif b/swt/org.argeo.cms.swt/icons/getSize.gif deleted file mode 100644 index b05bf3e3d..000000000 Binary files a/swt/org.argeo.cms.swt/icons/getSize.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/group.png b/swt/org.argeo.cms.swt/icons/group.png deleted file mode 100644 index cc6683aff..000000000 Binary files a/swt/org.argeo.cms.swt/icons/group.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/home.gif b/swt/org.argeo.cms.swt/icons/home.gif deleted file mode 100644 index fd0c66950..000000000 Binary files a/swt/org.argeo.cms.swt/icons/home.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/home.png b/swt/org.argeo.cms.swt/icons/home.png deleted file mode 100644 index 5eb096790..000000000 Binary files a/swt/org.argeo.cms.swt/icons/home.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/import_fs.png b/swt/org.argeo.cms.swt/icons/import_fs.png deleted file mode 100644 index d7c890c81..000000000 Binary files a/swt/org.argeo.cms.swt/icons/import_fs.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/installed.gif b/swt/org.argeo.cms.swt/icons/installed.gif deleted file mode 100644 index 298871653..000000000 Binary files a/swt/org.argeo.cms.swt/icons/installed.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/log.gif b/swt/org.argeo.cms.swt/icons/log.gif deleted file mode 100644 index e3ecc5535..000000000 Binary files a/swt/org.argeo.cms.swt/icons/log.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/logout.png b/swt/org.argeo.cms.swt/icons/logout.png deleted file mode 100644 index f2952fa5b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/logout.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/maintenance.gif b/swt/org.argeo.cms.swt/icons/maintenance.gif deleted file mode 100644 index e5690ecb1..000000000 Binary files a/swt/org.argeo.cms.swt/icons/maintenance.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/node.gif b/swt/org.argeo.cms.swt/icons/node.gif deleted file mode 100644 index 364c0e70b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/node.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/nodes.gif b/swt/org.argeo.cms.swt/icons/nodes.gif deleted file mode 100644 index bba3dbc69..000000000 Binary files a/swt/org.argeo.cms.swt/icons/nodes.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/osgi_explorer.gif b/swt/org.argeo.cms.swt/icons/osgi_explorer.gif deleted file mode 100644 index e9a6bd966..000000000 Binary files a/swt/org.argeo.cms.swt/icons/osgi_explorer.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/password.gif b/swt/org.argeo.cms.swt/icons/password.gif deleted file mode 100644 index a6b251fc8..000000000 Binary files a/swt/org.argeo.cms.swt/icons/password.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/person-logged-in.png b/swt/org.argeo.cms.swt/icons/person-logged-in.png deleted file mode 100644 index 87acc1435..000000000 Binary files a/swt/org.argeo.cms.swt/icons/person-logged-in.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/person.png b/swt/org.argeo.cms.swt/icons/person.png deleted file mode 100644 index 7d979a531..000000000 Binary files a/swt/org.argeo.cms.swt/icons/person.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/query.png b/swt/org.argeo.cms.swt/icons/query.png deleted file mode 100644 index 54c089de1..000000000 Binary files a/swt/org.argeo.cms.swt/icons/query.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/refresh.png b/swt/org.argeo.cms.swt/icons/refresh.png deleted file mode 100644 index 71b3481c9..000000000 Binary files a/swt/org.argeo.cms.swt/icons/refresh.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/remote_connected.gif b/swt/org.argeo.cms.swt/icons/remote_connected.gif deleted file mode 100644 index 1492b4efa..000000000 Binary files a/swt/org.argeo.cms.swt/icons/remote_connected.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/remote_disconnected.gif b/swt/org.argeo.cms.swt/icons/remote_disconnected.gif deleted file mode 100644 index 6c54da9ad..000000000 Binary files a/swt/org.argeo.cms.swt/icons/remote_disconnected.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/remove.gif b/swt/org.argeo.cms.swt/icons/remove.gif deleted file mode 100644 index 0ae6decd0..000000000 Binary files a/swt/org.argeo.cms.swt/icons/remove.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/removePrivileges.gif b/swt/org.argeo.cms.swt/icons/removePrivileges.gif deleted file mode 100644 index aa78fd2fa..000000000 Binary files a/swt/org.argeo.cms.swt/icons/removePrivileges.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/rename.gif b/swt/org.argeo.cms.swt/icons/rename.gif deleted file mode 100644 index 8048405a7..000000000 Binary files a/swt/org.argeo.cms.swt/icons/rename.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/repositories.gif b/swt/org.argeo.cms.swt/icons/repositories.gif deleted file mode 100644 index c13bea1ca..000000000 Binary files a/swt/org.argeo.cms.swt/icons/repositories.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/repository_connected.gif b/swt/org.argeo.cms.swt/icons/repository_connected.gif deleted file mode 100644 index a15fa5538..000000000 Binary files a/swt/org.argeo.cms.swt/icons/repository_connected.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/repository_disconnected.gif b/swt/org.argeo.cms.swt/icons/repository_disconnected.gif deleted file mode 100644 index 4576dc563..000000000 Binary files a/swt/org.argeo.cms.swt/icons/repository_disconnected.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/resolved.gif b/swt/org.argeo.cms.swt/icons/resolved.gif deleted file mode 100644 index f4a1ea150..000000000 Binary files a/swt/org.argeo.cms.swt/icons/resolved.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/role.gif b/swt/org.argeo.cms.swt/icons/role.gif deleted file mode 100644 index 274a850e4..000000000 Binary files a/swt/org.argeo.cms.swt/icons/role.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/rollback.gif b/swt/org.argeo.cms.swt/icons/rollback.gif deleted file mode 100755 index c75399599..000000000 Binary files a/swt/org.argeo.cms.swt/icons/rollback.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/save-all.png b/swt/org.argeo.cms.swt/icons/save-all.png deleted file mode 100644 index b68a29b2c..000000000 Binary files a/swt/org.argeo.cms.swt/icons/save-all.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/save.gif b/swt/org.argeo.cms.swt/icons/save.gif deleted file mode 100644 index 654ad7b42..000000000 Binary files a/swt/org.argeo.cms.swt/icons/save.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/save.png b/swt/org.argeo.cms.swt/icons/save.png deleted file mode 100644 index f27ef2d26..000000000 Binary files a/swt/org.argeo.cms.swt/icons/save.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/save_security.png b/swt/org.argeo.cms.swt/icons/save_security.png deleted file mode 100644 index ca41dc92b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/save_security.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/save_security_disabled.png b/swt/org.argeo.cms.swt/icons/save_security_disabled.png deleted file mode 100644 index fb7d08d9a..000000000 Binary files a/swt/org.argeo.cms.swt/icons/save_security_disabled.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/security.gif b/swt/org.argeo.cms.swt/icons/security.gif deleted file mode 100644 index 57fb95edc..000000000 Binary files a/swt/org.argeo.cms.swt/icons/security.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/service_published.gif b/swt/org.argeo.cms.swt/icons/service_published.gif deleted file mode 100644 index 17f771aff..000000000 Binary files a/swt/org.argeo.cms.swt/icons/service_published.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/service_referenced.gif b/swt/org.argeo.cms.swt/icons/service_referenced.gif deleted file mode 100644 index c24a95fba..000000000 Binary files a/swt/org.argeo.cms.swt/icons/service_referenced.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/sort.gif b/swt/org.argeo.cms.swt/icons/sort.gif deleted file mode 100644 index 23c5d0b11..000000000 Binary files a/swt/org.argeo.cms.swt/icons/sort.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/starting.gif b/swt/org.argeo.cms.swt/icons/starting.gif deleted file mode 100644 index 563743d39..000000000 Binary files a/swt/org.argeo.cms.swt/icons/starting.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/sync.gif b/swt/org.argeo.cms.swt/icons/sync.gif deleted file mode 100644 index b4fa052de..000000000 Binary files a/swt/org.argeo.cms.swt/icons/sync.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/user.gif b/swt/org.argeo.cms.swt/icons/user.gif deleted file mode 100644 index 90a00147b..000000000 Binary files a/swt/org.argeo.cms.swt/icons/user.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/users.gif b/swt/org.argeo.cms.swt/icons/users.gif deleted file mode 100644 index 2de7edd64..000000000 Binary files a/swt/org.argeo.cms.swt/icons/users.gif and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/workgroup.png b/swt/org.argeo.cms.swt/icons/workgroup.png deleted file mode 100644 index 7fef996df..000000000 Binary files a/swt/org.argeo.cms.swt/icons/workgroup.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/workgroup.xcf b/swt/org.argeo.cms.swt/icons/workgroup.xcf deleted file mode 100644 index f517c827c..000000000 Binary files a/swt/org.argeo.cms.swt/icons/workgroup.xcf and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/workspace_connected.png b/swt/org.argeo.cms.swt/icons/workspace_connected.png deleted file mode 100644 index 0430baaf5..000000000 Binary files a/swt/org.argeo.cms.swt/icons/workspace_connected.png and /dev/null differ diff --git a/swt/org.argeo.cms.swt/icons/workspace_disconnected.png b/swt/org.argeo.cms.swt/icons/workspace_disconnected.png deleted file mode 100644 index fddcb8c4e..000000000 Binary files a/swt/org.argeo.cms.swt/icons/workspace_disconnected.png and /dev/null differ 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 index 33841a1bb..ad347e6e8 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -65,7 +66,7 @@ public class CmsWizardDialog extends LightweightDialog implements IWizardContain 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)); + 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(); @@ -207,7 +208,7 @@ public class CmsWizardDialog extends LightweightDialog implements IWizardContain protected void finishPressed() { if (wizard.performFinish()) - closeShell(OK); + closeShell(CmsDialog.OK); } private static void setSwitchingFormData(Composite composite) { 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 index ddb6e1b33..06bb9be37 100644 --- 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 @@ -17,8 +17,8 @@ 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.auth.CurrentUser; -import org.argeo.util.CurrentSubject; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.util.CurrentSubject; import org.eclipse.swt.widgets.Display; public abstract class AbstractSwtCmsView implements CmsView { 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 index 2427c7610..cf05f6f64 100644 --- 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 @@ -9,7 +9,7 @@ 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.auth.CurrentUser; +import org.argeo.cms.CurrentUser; import org.argeo.cms.swt.SwtEditablePart; import org.argeo.cms.swt.widgets.ScrolledPage; import org.eclipse.swt.SWT; 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 index 951889eee..4a35a3bdd 100644 --- 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 @@ -10,7 +10,14 @@ public class ContentComposite extends Composite { public ContentComposite(Composite parent, int style, Content item) { super(parent, style); - setData(item); + if (item != null) + setData(item); + } + + public boolean hasContent() { + if (getData() == null) + return false; + return getData() instanceof Content; } public Content getContent() { 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 index 57c4da00c..f4f5961ca 100644 --- 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 @@ -27,10 +27,14 @@ public class SwtSection extends ContentComposite { 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) { + if (parentSection != null && hasContent() && parentSection.hasContent()) { relativeDepth = getProvidedContent().getDepth() - parentSection.getProvidedContent().getDepth(); } else { relativeDepth = 0; 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 index cd4e37d19..b65bc3b6a 100644 --- 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 @@ -40,6 +40,7 @@ public class SwtTabbedArea extends Composite { private StackLayout stackLayout; private boolean singleTab = false; + private String singleTabTitle = null; public SwtTabbedArea(Composite parent, int style) { super(parent, SWT.NONE); @@ -86,13 +87,15 @@ public class SwtTabbedArea extends Composite { Button title = new Button(sectionHeader, SWT.FLAT); CmsSwtUtils.style(title, selected ? tabSelectedStyle : tabStyle); title.setLayoutData(CmsSwtUtils.fillWidth()); - title.addSelectionListener((Selected) (e) -> showTab(tabIndex(section.getNode()))); + 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(); - - // TODO internationalize + if (singleTab && singleTabTitle != null) + titleStr = singleTabTitle; + + // TODO internationalise title.setText(titleStr); if (!singleTab) { ToolBar toolBar = new ToolBar(sectionHeader, SWT.NONE); @@ -119,7 +122,7 @@ public class SwtTabbedArea extends Composite { return; } SwtSection section = (SwtSection) body.getChildren()[0]; - previousNode = (ProvidedContent) section.getNode(); + previousNode = (ProvidedContent) section.getContent(); if (previousNode == null) {// empty state previousNode = (ProvidedContent) context; previousUiProvider = uiProvider; @@ -229,7 +232,7 @@ public class SwtTabbedArea extends Composite { public Content getCurrentContext() { SwtSection section = getCurrentSection(); if (section != null) { - return section.getNode(); + return section.getContent(); } else { return null; } @@ -255,4 +258,8 @@ public class SwtTabbedArea extends Composite { this.singleTab = singleTab; } + public void setSingleTabTitle(String singleTabTitle) { + this.singleTabTitle = singleTabTitle; + } + } 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 index e66549216..a3d533e2f 100644 --- 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 @@ -10,8 +10,8 @@ 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.SwtTreeView; 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; @@ -37,6 +37,13 @@ public class AcrContentTreeView extends Composite { 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); 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 index b0c36c602..37d88f5c3 100644 --- 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 @@ -6,7 +6,7 @@ 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.argeo.cms.swt.dialogs.LightweightDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; 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 index dedf61dea..abb8227cd 100644 --- 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 @@ -4,10 +4,11 @@ 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.CmsUserManager; 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; @@ -46,17 +47,17 @@ public class ChangePasswordDialog extends CmsMessageDialog { if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) { try { cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars()); - return OK; + return CmsDialog.OK; } catch (Exception e1) { log.error("Could not change password", e1); cancel(); CmsMessageDialog.openError(CmsMsg.invalidPassword.lead()); - return CANCEL; + return CmsDialog.CANCEL; } } else { cancel(); CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead()); - return CANCEL; + return CmsDialog.CANCEL; } }; @@ -67,7 +68,7 @@ public class ChangePasswordDialog extends CmsMessageDialog { @Override protected void okPressed() { Integer returnCode = cmsView.doAs(doIt); - if (returnCode.equals(OK)) { + if (returnCode.equals(CmsDialog.OK)) { super.okPressed(); CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead()); } 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 index 91885c74b..2fed95199 100644 --- 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 @@ -7,6 +7,7 @@ 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; @@ -85,7 +86,7 @@ public class CmsFeedback extends LightweightDialog { 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)); + 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); 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 index 66e640595..21308824d 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -80,7 +81,7 @@ public class CmsMessageDialog extends LightweightDialog { 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.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.OK)); close.setFocus(); close.addTraverseListener(traverseListener); @@ -124,15 +125,15 @@ public class CmsMessageDialog extends LightweightDialog { } protected void okPressed() { - closeShell(OK); + closeShell(CmsDialog.OK); } protected void cancelPressed() { - closeShell(CANCEL); + closeShell(CmsDialog.CANCEL); } protected void cancel() { - closeShell(CANCEL); + closeShell(CmsDialog.CANCEL); } protected Point getInitialSize() { 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 index d74be6aa2..3aec22a14 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -20,11 +21,6 @@ import org.eclipse.swt.widgets.Shell; 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; @@ -88,7 +84,7 @@ public class LightweightDialog { if (hasChildShells()) return; if (returnCode == null)// not yet closed - closeShell(CANCEL); + closeShell(CmsDialog.CANCEL); } @Override @@ -113,7 +109,7 @@ public class LightweightDialog { if (hasChildShells()) return; if (returnCode == null)// not yet closed - closeShell(CANCEL); + closeShell(CmsDialog.CANCEL); } }); backgroundShell.addDisposeListener((event) -> onClose()); @@ -122,7 +118,7 @@ public class LightweightDialog { block(); } if (returnCode == null) - returnCode = OK; + returnCode = CmsDialog.OK; return returnCode; } @@ -130,11 +126,11 @@ public class LightweightDialog { try { runEventLoop(foregoundShell); } catch (ThreadDeath t) { - returnCode = CANCEL; + returnCode = CmsDialog.CANCEL; if (log.isTraceEnabled()) log.error("Thread death, canceling dialog", t); } catch (Throwable t) { - returnCode = CANCEL; + returnCode = CmsDialog.CANCEL; log.error("Cannot open blocking lightweight dialog", t); } } @@ -162,13 +158,13 @@ public class LightweightDialog { private synchronized void notifyClose() { if (returnCode == null) - returnCode = CANCEL; + returnCode = CmsDialog.CANCEL; notifyAll(); } protected void closeShell(int returnCode) { this.returnCode = returnCode; - if (CANCEL == returnCode) + if (CmsDialog.CANCEL == returnCode) onCancel(); if (foregoundShell != null && !foregoundShell.isDisposed()) { foregoundShell.close(); diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java deleted file mode 100644 index 23e41eada..000000000 --- a/swt/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.SYSTEM_ROLES_BASEDN + ")))"; - - if (filterBuilder.length() > 1) { - builder.append("(&" + typeStr); - builder.append("(|"); - builder.append(filterBuilder.toString()); - builder.append("))"); - } else { - builder.append(typeStr); - } - roles = userAdmin.getRoles(builder.toString()); - } catch (InvalidSyntaxException e) { - throw new EclipseUiException( - "Unable to get roles with filter: " + filter, e); - } - List 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/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java deleted file mode 100644 index b3ab40ec3..000000000 --- a/swt/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.SYSTEM_ROLES_BASEDN)) - return UsersImages.ICON_ROLE; - else if (user.getType() == Role.GROUP) - return UsersImages.ICON_GROUP; - else - return UsersImages.ICON_USER; - } else - return null; - } - - @Override - public String getText(Object element) { - User user = (User) element; - return getText(user); - - } - - public String getText(User user) { - if (COL_DN.equals(currType)) - return user.getName(); - else if (COL_DISPLAY_NAME.equals(currType)) - return UserAdminUtils.getCommonName(user); - else if (COL_DOMAIN.equals(currType)) - return UserAdminUtils.getDomainName(user); - else - return ""; - } -} diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java deleted file mode 100644 index 21fc5afba..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java deleted file mode 100644 index 3597bfc57..000000000 --- a/swt/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/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 index efdc45a8a..e3a268153 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -16,8 +17,8 @@ public abstract class AbstractSwtView extends Composite implements protected final SelectionListener selectionListener; @SuppressWarnings("unchecked") - public AbstractSwtView(Composite parent, int style, DataPart dataPart) { - super(parent, style); + public AbstractSwtView(Composite parent, DataPart dataPart) { + super(parent, SWT.NONE); setLayout(CmsSwtUtils.noSpaceGridLayout()); this.dataPart = dataPart; 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 index a7242b9a9..15d531de1 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -65,7 +66,7 @@ public class SwtGuidedFormDialog extends LightweightDialog implements GuidedForm 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)); + 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(); @@ -191,7 +192,7 @@ public class SwtGuidedFormDialog extends LightweightDialog implements GuidedForm protected void finishPressed() { if (guidedForm.performFinish()) - closeShell(OK); + closeShell(CmsDialog.OK); } private static void setSwitchingFormData(Composite composite) { 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 index b136f36e1..3291980f6 100644 --- 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 @@ -22,10 +22,10 @@ public class SwtTableView extends AbstractSwtView { private CmsSwtTheme theme; public SwtTableView(Composite parent, int style, TabularPart tabularPart) { - super(parent, style, tabularPart); + super(parent, tabularPart); theme = CmsSwtUtils.getCmsTheme(parent); - table = new Table(this, SWT.VIRTUAL | SWT.BORDER); + table = new Table(this, SWT.VIRTUAL | style); table.setLinesVisible(true); table.setLayoutData(CmsSwtUtils.fillAll()); @@ -60,9 +60,9 @@ public class SwtTableView extends AbstractSwtView { 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); - T data = tabularPart.getData(row); item.setData(data); String text = data != null ? column.getText(data) : ""; if (text != null) @@ -75,6 +75,11 @@ public class SwtTableView extends AbstractSwtView { } } + @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 index 79543e9f8..778ed4138 100644 --- 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 @@ -2,9 +2,11 @@ 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; @@ -14,6 +16,8 @@ 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; @@ -22,10 +26,10 @@ public class SwtTreeView extends AbstractSwtView { private CmsSwtTheme theme; public SwtTreeView(Composite parent, int style, HierarchicalPart hierarchicalPart) { - super(parent, style, hierarchicalPart); + super(parent, hierarchicalPart); theme = CmsSwtUtils.getCmsTheme(parent); - tree = new Tree(this, SWT.BORDER); + tree = new Tree(this, style); tree.setLayoutData(CmsSwtUtils.fillAll()); this.hierarchicalPart = hierarchicalPart; @@ -42,7 +46,12 @@ public class SwtTreeView extends AbstractSwtView { List rootItems = hierarchicalPart.getChildren(hierarchicalPart.getInput()); for (T child : rootItems) { - addTreeItem(null, child); + try { + addTreeItem(null, child); + } catch (Exception e) { + if (log.isTraceEnabled()) + log.error("Cannot retrieve child", e); + } } tree.addListener(SWT.Expand, event -> { @@ -68,21 +77,30 @@ public class SwtTreeView extends AbstractSwtView { protected TreeItem addTreeItem(TreeItem parent, T data) { TreeItem item = parent == null ? new TreeItem(tree, SWT.NONE) : new TreeItem(parent, SWT.NONE); item.setData(data); - String txt = hierarchicalPart.getText(data); - if (txt != null) - item.setText(hierarchicalPart.getText(data)); - CmsIcon icon = hierarchicalPart.getIcon(data); - if (icon != null) { - Image image = theme.getSmallIcon(icon); - item.setImage(image); + 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 optimize + // 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/cms/ui/theme/CmsImages.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java deleted file mode 100644 index 1c4d79eee..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java deleted file mode 100644 index 7d3a260f3..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java deleted file mode 100644 index a388e745e..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java deleted file mode 100644 index f2715bc05..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java deleted file mode 100644 index 615e1417a..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java deleted file mode 100644 index 8ce9b44fb..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java deleted file mode 100644 index d6ab1481e..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java deleted file mode 100644 index 57139056c..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java deleted file mode 100644 index 9e93b1106..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java deleted file mode 100644 index 8f4df1799..000000000 --- a/swt/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/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java deleted file mode 100644 index 798d17482..000000000 --- a/swt/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/swt/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml b/swt/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml deleted file mode 100644 index 1f688baa6..000000000 --- a/swt/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/swt/rap/org.argeo.cms.e4.rap/bnd.bnd b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd index 29ce1708a..6db081fc2 100644 --- a/swt/rap/org.argeo.cms.e4.rap/bnd.bnd +++ b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd @@ -1,6 +1,3 @@ -Bundle-ActivationPolicy: lazy -Service-Component: OSGI-INF/cms-admin-rap.xml - Import-Package: \ org.argeo.api.acr, \ org.eclipse.swt,\ @@ -8,4 +5,5 @@ 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/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java deleted file mode 100644 index a3e127ab0..000000000 --- a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java +++ /dev/null @@ -1,16 +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")); - } - -} 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 index 7d4cd8331..cdd87fd3f 100644 --- 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 @@ -15,7 +15,7 @@ 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.auth.CurrentUser; +import org.argeo.cms.CurrentUser; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.cms.swt.SimpleSwtUxContext; import org.argeo.cms.swt.acr.AcrSwtImageManager; 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 index 1bca333c9..764234e18 100644 --- 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 @@ -2,7 +2,6 @@ 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; @@ -21,7 +20,8 @@ public class SimpleRapE4App extends AbstractRapE4App { String p = paths.nextElement(); if (p.endsWith(".e4xmi")) { String e4xmiPath = bundle.getSymbolicName() + '/' + p; - String name = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(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.swt.rap.cli/.classpath b/swt/rap/org.argeo.cms.swt.rap.cli/.classpath deleted file mode 100644 index 81fe078c2..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/.project b/swt/rap/org.argeo.cms.swt.rap.cli/.project deleted file mode 100644 index 7b3af9dc9..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.swt.rap.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/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/jni-config.json b/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/jni-config.json deleted file mode 100644 index 25530bb80..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/jni-config.json +++ /dev/null @@ -1,33 +0,0 @@ -[ -{ - "name":"java.lang.Boolean", - "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.ClassLoader", - "methods":[ - {"name":"getPlatformClassLoader","parameterTypes":[] }, - {"name":"loadClass","parameterTypes":["java.lang.String"] } - ] -}, -{ - "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" -}, -{ - "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", - "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] -}, -{ - "name":"sun.management.VMManagementImpl", - "fields":[ - {"name":"compTimeMonitoringSupport"}, - {"name":"currentThreadCpuTimeSupport"}, - {"name":"objectMonitorUsageSupport"}, - {"name":"otherThreadCpuTimeSupport"}, - {"name":"remoteDiagnosticCommandsSupport"}, - {"name":"synchronizerUsageSupport"}, - {"name":"threadAllocatedMemorySupport"}, - {"name":"threadContentionMonitoringSupport"} - ] -} -] diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/predefined-classes-config.json b/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/predefined-classes-config.json deleted file mode 100644 index 0e79b2c5d..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/predefined-classes-config.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "type":"agent-extracted", - "classes":[ - ] - } -] - diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/proxy-config.json b/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/proxy-config.json deleted file mode 100644 index 5d1d13de3..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/proxy-config.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "interfaces":["javax.servlet.http.HttpServletRequest"]} - , - { - "interfaces":["javax.servlet.http.HttpServletResponse"]} - -] diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/reflect-config.json b/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/reflect-config.json deleted file mode 100644 index e9910e1c2..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/reflect-config.json +++ /dev/null @@ -1,393 +0,0 @@ -[ -{ - "name":"[B" -}, -{ - "name":"[Ljava.lang.String;" -}, -{ - "name":"[Lorg.eclipse.swt.widgets.TableColumn;" -}, -{ - "name":"[Lorg.eclipse.swt.widgets.TreeColumn;" -}, -{ - "name":"[Lsun.security.pkcs.SignerInfo;" -}, -{ - "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"java.lang.Boolean", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.Byte", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.Class" -}, -{ - "name":"java.lang.Double", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.Float", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.Integer", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.Long", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.Object", - "queriedMethods":[ - {"name":"equals","parameterTypes":["java.lang.Object"] }, - {"name":"hashCode","parameterTypes":[] }, - {"name":"toString","parameterTypes":[] } - ] -}, -{ - "name":"java.lang.Short", - "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.lang.String" -}, -{ - "name":"java.lang.management.ManagementFactory", - "methods":[{"name":"getRuntimeMXBean","parameterTypes":[] }] -}, -{ - "name":"java.lang.management.RuntimeMXBean", - "methods":[{"name":"getUptime","parameterTypes":[] }] -}, -{ - "name":"java.security.AlgorithmParametersSpi" -}, -{ - "name":"java.security.SecureRandomParameters" -}, -{ - "name":"java.util.Date" -}, -{ - "name":"javax.security.auth.login.Configuration$Parameters" -}, -{ - "name":"javax.security.auth.x500.X500Principal", - "fields":[{"name":"thisX500Name"}], - "queriedMethods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] -}, -{ - "name":"javax.servlet.ServletRequest", - "queriedMethods":[ - {"name":"getAsyncContext","parameterTypes":[] }, - {"name":"getAttribute","parameterTypes":["java.lang.String"] }, - {"name":"getAttributeNames","parameterTypes":[] }, - {"name":"getCharacterEncoding","parameterTypes":[] }, - {"name":"getContentLength","parameterTypes":[] }, - {"name":"getContentLengthLong","parameterTypes":[] }, - {"name":"getContentType","parameterTypes":[] }, - {"name":"getDispatcherType","parameterTypes":[] }, - {"name":"getInputStream","parameterTypes":[] }, - {"name":"getLocalAddr","parameterTypes":[] }, - {"name":"getLocalName","parameterTypes":[] }, - {"name":"getLocalPort","parameterTypes":[] }, - {"name":"getLocale","parameterTypes":[] }, - {"name":"getLocales","parameterTypes":[] }, - {"name":"getParameter","parameterTypes":["java.lang.String"] }, - {"name":"getParameterMap","parameterTypes":[] }, - {"name":"getParameterNames","parameterTypes":[] }, - {"name":"getParameterValues","parameterTypes":["java.lang.String"] }, - {"name":"getProtocol","parameterTypes":[] }, - {"name":"getReader","parameterTypes":[] }, - {"name":"getRealPath","parameterTypes":["java.lang.String"] }, - {"name":"getRemoteAddr","parameterTypes":[] }, - {"name":"getRemoteHost","parameterTypes":[] }, - {"name":"getRemotePort","parameterTypes":[] }, - {"name":"getRequestDispatcher","parameterTypes":["java.lang.String"] }, - {"name":"getScheme","parameterTypes":[] }, - {"name":"getServerName","parameterTypes":[] }, - {"name":"getServerPort","parameterTypes":[] }, - {"name":"getServletContext","parameterTypes":[] }, - {"name":"isAsyncStarted","parameterTypes":[] }, - {"name":"isAsyncSupported","parameterTypes":[] }, - {"name":"isSecure","parameterTypes":[] }, - {"name":"removeAttribute","parameterTypes":["java.lang.String"] }, - {"name":"setAttribute","parameterTypes":["java.lang.String","java.lang.Object"] }, - {"name":"setCharacterEncoding","parameterTypes":["java.lang.String"] }, - {"name":"startAsync","parameterTypes":[] }, - {"name":"startAsync","parameterTypes":["javax.servlet.ServletRequest","javax.servlet.ServletResponse"] } - ] -}, -{ - "name":"javax.servlet.ServletResponse" -}, -{ - "name":"javax.servlet.http.HttpServletRequest", - "methods":[{"name":"","parameterTypes":["java.lang.reflect.InvocationHandler"] }], - "queriedMethods":[ - {"name":"authenticate","parameterTypes":["javax.servlet.http.HttpServletResponse"] }, - {"name":"changeSessionId","parameterTypes":[] }, - {"name":"getAuthType","parameterTypes":[] }, - {"name":"getContextPath","parameterTypes":[] }, - {"name":"getCookies","parameterTypes":[] }, - {"name":"getDateHeader","parameterTypes":["java.lang.String"] }, - {"name":"getHeader","parameterTypes":["java.lang.String"] }, - {"name":"getHeaderNames","parameterTypes":[] }, - {"name":"getHeaders","parameterTypes":["java.lang.String"] }, - {"name":"getHttpServletMapping","parameterTypes":[] }, - {"name":"getIntHeader","parameterTypes":["java.lang.String"] }, - {"name":"getMethod","parameterTypes":[] }, - {"name":"getPart","parameterTypes":["java.lang.String"] }, - {"name":"getParts","parameterTypes":[] }, - {"name":"getPathInfo","parameterTypes":[] }, - {"name":"getPathTranslated","parameterTypes":[] }, - {"name":"getQueryString","parameterTypes":[] }, - {"name":"getRemoteUser","parameterTypes":[] }, - {"name":"getRequestURI","parameterTypes":[] }, - {"name":"getRequestURL","parameterTypes":[] }, - {"name":"getRequestedSessionId","parameterTypes":[] }, - {"name":"getServletPath","parameterTypes":[] }, - {"name":"getSession","parameterTypes":[] }, - {"name":"getSession","parameterTypes":["boolean"] }, - {"name":"getTrailerFields","parameterTypes":[] }, - {"name":"getUserPrincipal","parameterTypes":[] }, - {"name":"isRequestedSessionIdFromCookie","parameterTypes":[] }, - {"name":"isRequestedSessionIdFromURL","parameterTypes":[] }, - {"name":"isRequestedSessionIdFromUrl","parameterTypes":[] }, - {"name":"isRequestedSessionIdValid","parameterTypes":[] }, - {"name":"isTrailerFieldsReady","parameterTypes":[] }, - {"name":"isUserInRole","parameterTypes":["java.lang.String"] }, - {"name":"login","parameterTypes":["java.lang.String","java.lang.String"] }, - {"name":"logout","parameterTypes":[] }, - {"name":"newPushBuilder","parameterTypes":[] }, - {"name":"upgrade","parameterTypes":["java.lang.Class"] } - ] -}, -{ - "name":"javax.servlet.http.HttpServletResponse" -}, -{ - "name":"org.apache.xerces.impl.dv.dtd.DTDDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.dv.dtd.XML11DTDDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.dv.xs.ExtendedSchemaDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.dv.xs.SchemaDVFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.impl.xs.XSMessageFormatter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.apache.xerces.parsers.XIncludeAwareParserConfiguration", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.argeo.cms.auth.AnonymousLoginModule", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.argeo.cms.auth.DataAdminLoginModule", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.argeo.cms.auth.IdentLoginModule", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.argeo.cms.auth.RemoteSessionLoginModule", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.argeo.cms.auth.UserAdminLoginModule", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.jetty.servlet.DefaultServlet", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.jetty.util.TypeUtil", - "methods":[ - {"name":"getClassLoaderLocation","parameterTypes":["java.lang.Class"] }, - {"name":"getCodeSourceLocation","parameterTypes":["java.lang.Class"] }, - {"name":"getModuleLocation","parameterTypes":["java.lang.Class"] }, - {"name":"getSystemClassLoaderLocation","parameterTypes":["java.lang.Class"] } - ] -}, -{ - "name":"org.eclipse.rap.rwt.internal.client.BrowserNavigationImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.client.ClientInfoImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.client.ExitConfirmationImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.client.StartupParametersImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.client.WebClientMessages", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle", - "methods":[{"name":"","parameterTypes":["org.eclipse.rap.rwt.internal.application.ApplicationContextImpl"] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.lifecycle.RequestCounter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.remote.RemoteObjectRegistry", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.serverpush.ServerPushManager", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.rap.rwt.internal.textsize.ProbeResultStore", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.SWT", - "allDeclaredFields":true -}, -{ - "name":"org.eclipse.swt.graphics.Color", - "methods":[{"name":"","parameterTypes":["int"] }] -}, -{ - "name":"org.eclipse.swt.graphics.Font", - "methods":[{"name":"","parameterTypes":["org.eclipse.swt.graphics.FontData"] }] -}, -{ - "name":"org.eclipse.swt.internal.image.GIFFileFormat", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.image.JPEGFileFormat", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.image.PNGFileFormat", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.image.WinBMPFileFormat", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.image.WinICOFileFormat", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.widgets.IdGenerator", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.widgets.displaykit.DisplayLCA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.widgets.scrollbarkit.ScrollBarThemeAdapter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.widgets.shellkit.ShellThemeAdapter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.widgets.tablekit.TableThemeAdapter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.eclipse.swt.internal.widgets.treekit.TreeThemeAdapter", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.ConfigFile$Spi", - "methods":[{"name":"","parameterTypes":["javax.security.auth.login.Configuration$Parameters"] }] -}, -{ - "name":"sun.security.provider.DRBG", - "methods":[{"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] -}, -{ - "name":"sun.security.provider.DSAKeyFactory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.DSAParameters", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.X509Factory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSAKeyFactory$Legacy", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.util.ObjectIdentifier" -}, -{ - "name":"sun.security.x509.AuthorityKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.BasicConstraintsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CertificateExtensions" -}, -{ - "name":"sun.security.x509.ExtendedKeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.KeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.SubjectKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -} -] diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/resource-config.json b/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/resource-config.json deleted file mode 100644 index 0f3300a5a..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/resource-config.json +++ /dev/null @@ -1,735 +0,0 @@ -{ - "resources":{ - "includes":[ - { - "pattern":"\\QMETA-INF/MANIFEST.MF\\E" - }, - { - "pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E" - }, - { - "pattern":"\\QMETA-INF/services/javax.xml.validation.SchemaFactory\\E" - }, - { - "pattern":"\\QMETA-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder\\E" - }, - { - "pattern":"\\Qclient.files\\E" - }, - { - "pattern":"\\Qclient.js\\E" - }, - { - "pattern":"\\Qjetty-dir.css\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/DSMLv2.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/SVG.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/XForms-11-Schema.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/XMLSchema.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/cr.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/docbook.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/schema-for-xslt20.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/xlink.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/xml-events-attribs-1.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/acr/schemas/xml.xsd\\E" - }, - { - "pattern":"\\Qorg/argeo/cms/internal/runtime/jaas.cfg\\E" - }, - { - "pattern":"\\Qorg/eclipse/jetty/http/encoding.properties\\E" - }, - { - "pattern":"\\Qorg/eclipse/jetty/http/mime.properties\\E" - }, - { - "pattern":"\\Qorg/eclipse/jetty/version/build.properties\\E" - }, - { - "pattern":"\\Qorg/eclipse/nebula/widgets/grid/internal/gridkit/Grid.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/nebula/widgets/grid/internal/gridkit/Grid.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/nebula/widgets/grid/internal/gridkit/Grid.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/service/rwt-index.html\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/dropdownkit/DropDown.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/dropdownkit/DropDown.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/dropdownkit/DropDown.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/fileuploadkit/FileUpload.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/fileuploadkit/FileUpload.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/fileuploadkit/FileUpload.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/browser/browserkit/Browser.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/browser/browserkit/Browser.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/browser/browserkit/Browser.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/ccombokit/CCombo.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/ccombokit/CCombo.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/ccombokit/CCombo.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/clabelkit/CLabel.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/clabelkit/CLabel.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/clabelkit/CLabel.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/ctabfolderkit/CTabFolder.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/ctabfolderkit/CTabFolder.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/ctabfolderkit/CTabFolder.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/custom/scrolledcompositekit/ScrolledComposite.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/buttonkit/Button.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/buttonkit/Button.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/buttonkit/Button.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/combokit/Combo.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/combokit/Combo.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/combokit/Combo.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/compositekit/Composite.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/compositekit/Composite.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/compositekit/Composite.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/controlkit/Control.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/controlkit/Control.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/coolbarkit/CoolBar.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/coolbarkit/CoolBar.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/coolbarkit/CoolBar.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/datetimekit/DateTime.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/datetimekit/DateTime.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/datetimekit/DateTime.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/expandbarkit/ExpandBar.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/expandbarkit/ExpandBar.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/expandbarkit/ExpandBar.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/groupkit/Group.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/groupkit/Group.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/groupkit/Group.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/labelkit/Label.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/labelkit/Label.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/labelkit/Label.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/linkkit/Link.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/linkkit/Link.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/linkkit/Link.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/listkit/List.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/listkit/List.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/listkit/List.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/menukit/Menu.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/menukit/Menu.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/menukit/Menu.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/progressbarkit/ProgressBar.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/progressbarkit/ProgressBar.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/progressbarkit/ProgressBar.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/sashkit/Sash.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/sashkit/Sash.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/sashkit/Sash.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/scalekit/Scale.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/scalekit/Scale.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/scalekit/Scale.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/scrollbarkit/ScrollBar.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/scrollbarkit/ScrollBar.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/scrollbarkit/ScrollBar.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/shellkit/Shell.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/shellkit/Shell.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/shellkit/Shell.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/sliderkit/Slider.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/sliderkit/Slider.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/sliderkit/Slider.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/spinnerkit/Spinner.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/spinnerkit/Spinner.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/spinnerkit/Spinner.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tabfolderkit/TabFolder.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tabfolderkit/TabFolder.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tabfolderkit/TabFolder.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tablekit/Table.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tablekit/Table.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tablekit/Table.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/textkit/Text.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/textkit/Text.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/textkit/Text.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/toolbarkit/ToolBar.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/toolbarkit/ToolBar.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/toolbarkit/ToolBar.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tooltipkit/ToolTip.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tooltipkit/ToolTip.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/tooltipkit/ToolTip.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/treekit/Tree.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/treekit/Tree.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/treekit/Tree.theme.xml\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/widgetkit/Widget.appearances.js\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/widgetkit/Widget.default.css\\E" - }, - { - "pattern":"\\Qorg/eclipse/swt/internal/widgets/widgetkit/Widget.theme.xml\\E" - }, - { - "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" - }, - { - "pattern":"\\Qresource/static/html/blank.html\\E" - }, - { - "pattern":"\\Qresource/static/image/blank.gif\\E" - }, - { - "pattern":"\\Qresource/theme/default.css\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/chevron-left-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/chevron-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/chevron-right-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/chevron-right.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/tooltip-down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/tooltip-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/tooltip-right.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/arrows/tooltip-up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/arrow-down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/arrow-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/arrow-right.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/arrow-up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/check-grayed-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/check-grayed.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/check-selected-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/check-selected.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/check-unselected-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/check-unselected.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/radio-selected-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/radio-selected.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/radio-unselected-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/button/radio-unselected.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/lastMonth-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/lastMonth.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/lastYear-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/lastYear.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/nextMonth-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/nextMonth.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/nextYear-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/calendar/nextYear.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ccombo/down-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ccombo/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/column/sort-indicator-down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/column/sort-indicator-up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/combo/down-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/combo/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/close.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/close_hover.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown-left-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/maximize.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/minimize.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/ctabfolder/restore.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/cursors/alias.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/cursors/copy.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/cursors/move.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/cursors/nodrop.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/cursors/up_arrow.cur\\E" - }, - { - "pattern":"\\Qresource/widget/rap/datetime/down-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/datetime/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/datetime/up-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/datetime/up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/dialog/error.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/dialog/information.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/dialog/question.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/dialog/warning.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/display/browser_bg.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/display/loading.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/expanditem/expanditem-collapse-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/expanditem/expanditem-collapse.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/expanditem/expanditem-expand-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/expanditem/expanditem-expand.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/menu/arrow-left.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/menu/arrow-right.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/menu/checkbox.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/menu/radiobutton.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/progressbar/progressbar-background.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/sash/sash-handle-horizontal.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/sash/sash-handle-vertical.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scale/h_line.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scale/v_line.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scrollbar/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scrollbar/left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scrollbar/right.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scrollbar/scrollbar-background.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/scrollbar/up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/slider/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/slider/left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/slider/right.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/slider/slider-background.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/slider/up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/spinner/down-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/spinner/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/spinner/up-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/spinner/up.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/text/clear.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/text/find.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/toolbar/down.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tooltip/error.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tooltip/information.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tooltip/warning.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/loading.gif\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-collapsed-hover-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-collapsed-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-collapsed-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-collapsed.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-expanded-hover-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-expanded-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-expanded-left.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/tree/tree-expanded.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-close-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-close.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-max-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-max.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-min-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-min.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-restore-hover.png\\E" - }, - { - "pattern":"\\Qresource/widget/rap/window/shell-restore.png\\E" - } - ]}, - "bundles":[ - { - "name":"javax.servlet.LocalStrings", - "locales":[""] - }, - { - "name":"javax.servlet.http.LocalStrings", - "locales":[""] - }, - { - "name":"org.apache.xerces.impl.xpath.regex.message", - "locales":[ - "", - "en" - ] - }, - { - "name":"org.eclipse.rap.rwt.internal.RWTMessages", - "locales":[""] - }, - { - "name":"sun.security.util.Resources", - "classNames":["sun.security.util.Resources"] - } - ] -} diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/serialization-config.json b/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/serialization-config.json deleted file mode 100644 index bf554e062..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/META-INF/native-image/serialization-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "types":[ - ], - "lambdaCapturingTypes":[ - ] -} diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/bnd.bnd b/swt/rap/org.argeo.cms.swt.rap.cli/bnd.bnd deleted file mode 100644 index 4dce79bc7..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/bnd.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Import-Package: \ -org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\ -org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\ -* \ No newline at end of file diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/build.properties b/swt/rap/org.argeo.cms.swt.rap.cli/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java deleted file mode 100644 index 5191b7718..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/CmsRapCli.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.argeo.cms.swt.rap.cli; - -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.argeo.api.acr.spi.ProvidedRepository; -import org.argeo.api.cli.CommandsCli; -import org.argeo.api.cli.DescribedCommand; -import org.argeo.api.cms.CmsApp; -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsState; -import org.argeo.cms.jetty.CmsJettyServer; -import org.argeo.cms.runtime.StaticCms; -import org.argeo.cms.swt.app.CmsUserApp; -import org.argeo.cms.web.CmsWebApp; -import org.argeo.util.register.Component; -import org.argeo.util.register.ComponentRegister; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; - -public class CmsRapCli extends CommandsCli { - - public CmsRapCli(String commandName) { - super(commandName); - addCommand("user", new Launch()); - } - - @Override - public String getDescription() { - return "Argeo CMS utilities."; - } - - public static void main(String[] args) { - mainImpl(new CmsRapCli("web"), args); - } - - static class Launch implements DescribedCommand { - private Option dataOption; - private Option uiOption; - - @Override - public Options getOptions() { - Options options = new Options(); - dataOption = Option.builder().longOpt("data").hasArg().required() - .desc("path to the writable data area (mandatory)").build(); - uiOption = Option.builder().longOpt("ui").desc("open a user interface").build(); - options.addOption(dataOption); - options.addOption(uiOption); - return options; - } - - @Override - public String apply(List args) { - CommandLine cl = toCommandLine(args); - String dataPath = cl.getOptionValue(dataOption); - boolean ui = cl.hasOption(uiOption); - - Path instancePath = Paths.get(dataPath); - System.setProperty("osgi.instance.area", instancePath.toUri().toString()); - System.setProperty("argeo.http.port", "0"); - - StaticCms staticCms = new StaticCms() { - @Override - protected void addComponents(ComponentRegister register) { - if (ui) { - CmsUserApp cmsApp = new CmsUserApp(); - Component cmsAppC = new Component.Builder<>(cmsApp) // - .addType(CmsApp.class) // - .addType(CmsUserApp.class) // - .addDependency(register.getSingleton(CmsContext.class), cmsApp::setCmsContext, null) // - .addDependency(register.getSingleton(ProvidedRepository.class), - cmsApp::setContentRepository, null) // - .build(register); - - CmsWebApp cmsWebApp = new CmsWebApp(); - Component cmsWebAppC = new Component.Builder<>(cmsWebApp) // - .addType(ApplicationConfiguration.class) // - .addType(CmsWebApp.class) // - .addDependency(cmsAppC.getType(CmsApp.class), cmsWebApp::setCmsApp, null) // - .build(register); - - RapJettyServer rwtRunner = new RapJettyServer(); - Component rwtRunnerC = new Component.Builder<>(rwtRunner) // - .addActivation(rwtRunner::start) // - .addDeactivation(rwtRunner::stop) // - .addType(CmsJettyServer.class) // - .addDependency(register.getSingleton(CmsState.class), rwtRunner::setCmsState, null) // - .addDependency(cmsWebAppC.getType(CmsWebApp.class), rwtRunner::setCmsWebApp, null) // - .build(register); - } - } - - }; - Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown")); - staticCms.start(); - - long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - System.out.println("Static CMS available in " + jvmUptime + " ms."); - - if (ui) { - try { - // open browser in app mode - Thread.sleep(2000);// wait for RWT to be ready - String browserCommand = "google-chrome --app=http://localhost:" - + staticCms.getComponentRegister().getObject(CmsJettyServer.class).getHttpPort() + "/data"; - Runtime.getRuntime().exec(browserCommand); - } catch (InterruptedException | IOException e) { - e.printStackTrace(); - } - } - - staticCms.waitForStop(); - - return null; - } - - @Override - public String getDescription() { - return "Launch a static CMS."; - } - - } -} diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RapJettyServer.java b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RapJettyServer.java deleted file mode 100644 index 5b3337ace..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RapJettyServer.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.argeo.cms.swt.rap.cli; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.ServletException; - -import org.argeo.cms.jetty.CmsJettyServer; -import org.argeo.cms.web.CmsWebApp; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.rap.rwt.application.ApplicationRunner; -import org.eclipse.rap.rwt.engine.RWTServlet; - -public class RapJettyServer extends CmsJettyServer { - private CmsWebApp cmsWebApp; - - @Override - protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException { - // rwt-resources requires a file system - try { - Path tempDir = Files.createTempDirectory("argeo-rwtRunner"); - servletContextHandler.setBaseResource(Resource.newResource(tempDir.resolve("www").toString())); - } catch (IOException e) { - throw new IllegalStateException("Cannot create temporary directory", e); - } - servletContextHandler.addEventListener(new ServletContextListener() { - ApplicationRunner applicationRunner; - - @Override - public void contextInitialized(ServletContextEvent sce) { - applicationRunner = new ApplicationRunner(cmsWebApp, sce.getServletContext()); - applicationRunner.start(); - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - applicationRunner.stop(); - } - }); - for (String uiName : cmsWebApp.getCmsApp().getUiNames()) - servletContextHandler.addServlet(new ServletHolder(new RWTServlet()), "/" + uiName); - - // Required to serve rwt-resources. It is important that this is last. - ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); - servletContextHandler.addServlet(holderPwd, "/"); - - } - - public void setCmsWebApp(CmsWebApp cmsWebApp) { - this.cmsWebApp = cmsWebApp; - } - -} diff --git a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RwtRunner.java b/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RwtRunner.java deleted file mode 100644 index b03939f81..000000000 --- a/swt/rap/org.argeo.cms.swt.rap.cli/src/org/argeo/cms/swt/rap/cli/RwtRunner.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.argeo.cms.swt.rap.cli; - -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -import org.argeo.minidesktop.MiniDesktopManager; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.servlet.DefaultServlet; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.rap.rwt.application.ApplicationConfiguration; -import org.eclipse.rap.rwt.application.ApplicationRunner; -import org.eclipse.rap.rwt.application.EntryPoint; -import org.eclipse.rap.rwt.application.Application.OperationMode; -import org.eclipse.rap.rwt.engine.RWTServlet; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -/** A minimal RWT runner based on embedded Jetty. */ -public class RwtRunner { - - private final Server server; - private final ServerConnector serverConnector; - private Path tempDir; - - private ApplicationConfiguration applicationConfiguration; - - public RwtRunner() { - server = new Server(new QueuedThreadPool(10, 1)); - serverConnector = new ServerConnector(server); - serverConnector.setPort(0); - server.setConnectors(new Connector[] { serverConnector }); - } - - protected Control createUi(Composite parent, Object context) { - return new Label(parent, 0); - } - - public void init() { - Objects.requireNonNull(applicationConfiguration); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - server.setHandler(context); - - String entryPoint = "app"; - - // rwt-resources requires a file system - try { - tempDir = Files.createTempDirectory("argeo-rwtRunner"); - context.setBaseResource(Resource.newResource(tempDir.resolve("www").toString())); - } catch (IOException e) { - throw new IllegalStateException("Cannot create temporary directory", e); - } - context.addEventListener(new ServletContextListener() { - ApplicationRunner applicationRunner; - - @Override - public void contextInitialized(ServletContextEvent sce) { - applicationRunner = new ApplicationRunner(applicationConfiguration, sce.getServletContext()); - applicationRunner.start(); - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - applicationRunner.stop(); - } - }); - - context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint); - - // Required to serve rwt-resources. It is important that this is last. - ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class); - context.addServlet(holderPwd, "/"); - - try { - server.start(); - } catch (Exception e) { - throw new IllegalStateException("Cannot start Jetty server", e); - } - Runtime.getRuntime().addShutdownHook(new Thread(() -> destroy(), "Jetty shutdown")); - - long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - System.out.println("RWT App available in " + jvmUptime + " ms, on port " + getEffectivePort()); - } - - public void destroy() { - try { - serverConnector.close(); - server.stop(); - // TODO delete temp dir - } catch (Exception e) { - e.printStackTrace(); - } - } - - public Integer getEffectivePort() { - return serverConnector.getLocalPort(); - } - - public void waitFor() throws InterruptedException { - server.join(); - } - - public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) { - this.applicationConfiguration = applicationConfiguration; - } - - public static void main(String[] args) throws Exception { - RwtRunner rwtRunner = new RwtRunner(); - - String entryPoint = "app"; - ApplicationConfiguration applicationConfiguration = (application) -> { - application.setOperationMode(OperationMode.SWT_COMPATIBILITY); - application.addEntryPoint("/" + entryPoint, () -> new EntryPoint() { - @Override - public int createUI() { - MiniDesktopManager miniDesktopManager = new MiniDesktopManager(false, false); - miniDesktopManager.init(); - miniDesktopManager.run(); - return 0; - } - }, null); - }; - - rwtRunner.setApplicationConfiguration(applicationConfiguration); - rwtRunner.init(); - - // open browser in app mode - Thread.sleep(2000);// wait for RWT to be ready - Runtime.getRuntime().exec("google-chrome --app=http://localhost:" + rwtRunner.getEffectivePort() + "/app"); - - rwtRunner.waitFor(); - } -} diff --git a/swt/rap/org.argeo.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 index 31555d168..67fa5ceac 100644 --- 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 @@ -12,7 +12,7 @@ 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.util.LangUtils; +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; 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 index d63aeeea5..70f49f670 100644 --- 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 @@ -17,8 +17,8 @@ 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.CurrentUser; import org.argeo.cms.auth.RemoteAuthCallbackHandler; import org.argeo.cms.servlet.ServletHttpRequest; import org.argeo.cms.servlet.ServletHttpResponse; @@ -283,9 +283,9 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, } } 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 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; diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi index afaf71f98..6743a4e07 100644 --- a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi +++ b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi @@ -5,7 +5,7 @@ - + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd index ff79c8041..eaa51adec 100644 --- a/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd +++ b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd @@ -4,4 +4,5 @@ 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/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java index 16d3ca8d2..3861597aa 100644 --- 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 @@ -12,8 +12,7 @@ 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.auth.CurrentUser; -import org.argeo.cms.swt.CmsException; +import org.argeo.cms.CurrentUser; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.cms.swt.SimpleSwtUxContext; import org.argeo.cms.swt.auth.CmsLoginShell; @@ -156,7 +155,7 @@ public class CmsE4Application implements IApplication, CmsView { @Override public void authChange(LoginContext loginContext) { if (loginContext == null) - throw new CmsException("Login context cannot be null"); + throw new IllegalStateException("Login context cannot be null"); // logout previous login context // if (this.loginContext != null) // try { @@ -170,12 +169,12 @@ public class CmsE4Application implements IApplication, CmsView { @Override public void logout() { if (loginContext == null) - throw new CmsException("Login context should not bet null"); + throw new IllegalStateException("Login context should not bet null"); try { CurrentUser.logoutCmsSession(loginContext.getSubject()); loginContext.logout(); } catch (LoginException e) { - throw new CmsException("Cannot log out", e); + throw new IllegalStateException("Cannot log out", e); } } diff --git a/swt/rcp/org.argeo.cms.swt.rcp.cli/.classpath b/swt/rcp/org.argeo.cms.swt.rcp.cli/.classpath deleted file mode 100644 index 81fe078c2..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp.cli/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/swt/rcp/org.argeo.cms.swt.rcp.cli/.project b/swt/rcp/org.argeo.cms.swt.rcp.cli/.project deleted file mode 100644 index 07c8c5d91..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp.cli/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.swt.rcp.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/swt/rcp/org.argeo.cms.swt.rcp.cli/bnd.bnd b/swt/rcp/org.argeo.cms.swt.rcp.cli/bnd.bnd deleted file mode 100644 index e69de29bb..000000000 diff --git a/swt/rcp/org.argeo.cms.swt.rcp.cli/build.properties b/swt/rcp/org.argeo.cms.swt.rcp.cli/build.properties deleted file mode 100644 index 34d2e4d2d..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp.cli/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . diff --git a/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java b/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java deleted file mode 100644 index 3fe2935af..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.argeo.cms.swt.rcp.cli; - -import java.lang.management.ManagementFactory; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.concurrent.ForkJoinPool; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.argeo.api.acr.spi.ProvidedRepository; -import org.argeo.api.cli.CommandsCli; -import org.argeo.api.cli.DescribedCommand; -import org.argeo.api.cms.CmsApp; -import org.argeo.cms.runtime.StaticCms; -import org.argeo.cms.swt.app.CmsUserApp; -import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory; -import org.argeo.util.register.Component; -import org.argeo.util.register.ComponentRegister; - -public class CmsCli extends CommandsCli { - - public CmsCli(String commandName) { - super(commandName); - addCommand("static", new Launch()); - } - - @Override - public String getDescription() { - return "Argeo CMS utilities."; - } - - static class Launch implements DescribedCommand { - private Option dataOption; - private Option uiOption; - - @Override - public Options getOptions() { - Options options = new Options(); - dataOption = Option.builder().longOpt("data").hasArg().required() - .desc("path to the writable data area (mandatory)").build(); - uiOption = Option.builder().longOpt("ui").desc("open a user interface").build(); - options.addOption(dataOption); - options.addOption(uiOption); - return options; - } - - @Override - public String apply(List args) { - CommandLine cl = toCommandLine(args); - String dataPath = cl.getOptionValue(dataOption); - boolean ui = cl.hasOption(uiOption); - - Path instancePath = Paths.get(dataPath); - System.setProperty("osgi.instance.area", instancePath.toUri().toString()); - - StaticCms staticCms = new StaticCms() { - @Override - protected void addComponents(ComponentRegister register) { - if (ui) { - Component contentRepositoryC = register - .find(ProvidedRepository.class, null).first(); - CmsUserApp cmsApp = new CmsUserApp(); - Component cmsAppC = new Component.Builder<>(cmsApp) // - .addType(CmsApp.class) // - .addType(CmsUserApp.class) // - .addDependency(contentRepositoryC.getType(ProvidedRepository.class), - cmsApp::setContentRepository, null) // - .build(register); - - CmsRcpDisplayFactory displayFactory = new CmsRcpDisplayFactory(); - Component displayFactoryC = new Component.Builder<>(displayFactory) // - .addActivation(displayFactory::init) // - .addDeactivation(displayFactory::destroy) // - .build(register); - - } - } - - @Override - protected void postActivation(ComponentRegister register) { - if (ui) { - Component cmsAppC = register.find(CmsUserApp.class, null).first(); - CmsRcpDisplayFactory.openCmsApp(cmsAppC.get(), "data", (e) -> { - // asynchronous in order to avoid deadlock in UI thread - ForkJoinPool.commonPool().execute(() -> stop()); - }); - } - } - - }; - Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown")); - staticCms.start(); - - long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); - System.out.println("Static CMS available in " + jvmUptime + " ms."); - - staticCms.waitForStop(); - - return null; - } - - @Override - public String getDescription() { - return "Launch a static CMS."; - } - - } -} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml index a0c0f0f5c..8b1d14684 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml +++ b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml @@ -1,4 +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/OSGI-INF/cmsRcpServletFactory.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpServletFactory.xml deleted file mode 100644 index fe0bc64db..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpServletFactory.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd index 91f0a8a37..5cf5164e1 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd +++ b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd @@ -1,3 +1,4 @@ +Bundle-SymbolicName: org.argeo.cms.swt.rcp;singleton=true Import-Package:\ org.argeo.cms.auth,\ @@ -8,5 +9,5 @@ org.w3c.css.sac,\ Service-Component:\ OSGI-INF/cmsRcpDisplayFactory.xml,\ -OSGI-INF/cmsRcpServletFactory.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 index 5eef7058e..4ed1d4748 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/build.properties +++ b/swt/rcp/org.argeo.cms.swt.rcp/build.properties @@ -2,5 +2,5 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ OSGI-INF/,\ - OSGI-INF/cmsRcpServletFactory.xml + OSGI-INF/cmsRcpHttpLauncher.xml source.. = src/ 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 index 950accece..a83a54db3 100644 --- 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 @@ -1,14 +1,18 @@ 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.util.OS; +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"; @@ -50,20 +54,22 @@ public class CmsRcpDisplayFactory { @Override public void run() { - display = Display.getDefault(); - display.setRuntimeExceptionHandler((e) -> e.printStackTrace()); - display.setErrorHandler((e) -> e.printStackTrace()); - -// for (String contextName : cmsApps.keySet()) { -// openCmsApp(contextName); -// } - - while (!shutdown) { - if (!display.readAndDispatch()) - display.sleep(); + 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); } - display.dispose(); - display = null; } } 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.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java deleted file mode 100644 index 8579527c1..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServlet.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.cms.ui.rcp.servlet; - -import java.io.IOException; -import java.lang.System.Logger; -import java.lang.System.Logger.Level; -import java.util.Objects; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsApp; -import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory; - -/** Open the related app when called. */ -public class CmsRcpServlet extends HttpServlet { - private static final long serialVersionUID = -3944472431354848923L; - private final static Logger logger = System.getLogger(CmsRcpServlet.class.getName()); - - private CmsApp cmsApp; - - public CmsRcpServlet(CmsApp cmsApp) { - Objects.requireNonNull(cmsApp); - this.cmsApp = cmsApp; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String path = req.getPathInfo(); - String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : ""; - CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null); - logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App " + req.getServletPath()); - } - -} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java deleted file mode 100644 index 09b1e41b4..000000000 --- a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/servlet/CmsRcpServletFactory.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.argeo.cms.ui.rcp.servlet; - -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 org.argeo.api.cms.CmsApp; -import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory; - -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 CmsRcpServletFactory { - private final static Logger logger = System.getLogger(CmsRcpServletFactory.class.getName()); - private HttpServer httpServer; -// private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpServletFactory.class).getBundleContext(); -// -// private Map> registrations = Collections.synchronizedMap(new HashMap<>()); - - 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.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); - } - }); - logger.log(Level.DEBUG, "Registered RCP CMS APP /" + contextName); -// CmsRcpServlet servlet = new CmsRcpServlet(cmsApp); -// Hashtable serviceProperties = new Hashtable<>(); -// serviceProperties.put("osgi.http.whiteboard.servlet.pattern", "/" + contextName + "/*"); -// ServiceRegistration sr = bundleContext.registerService(Servlet.class, servlet, serviceProperties); -// registrations.put(contextName, sr); - } - } - - public void removeCmsApp(CmsApp cmsApp, Map properties) { - String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); - if (contextName != null) { - httpServer.removeContext("/" + contextName); -// ServiceRegistration sr = registrations.get(contextName); -// sr.unregister(); - } - } - - public void setHttpServer(HttpServer httpServer) { - this.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); - } - - 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/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 index 91109a9de..47ff35dc0 100644 --- 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 @@ -7,17 +7,16 @@ import java.util.Collections; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.io.IOUtils; +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()); + private Map register = Collections.synchronizedMap(new TreeMap()); @Override public void register(String name, InputStream in) { try { - register.put(name, IOUtils.toByteArray(in)); + register.put(name, StreamUtils.toByteArray(in)); } catch (IOException e) { throw new RuntimeException("Cannot register " + name, e); }