From: Mathieu Date: Thu, 12 Jan 2023 07:29:01 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/unstable' into testing X-Git-Tag: v2.1.111~1 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=4b524aca3798d121e67eaf84842a8de8f056cc0d;hp=3f739f18ea479565ce78ad1102db6804ed7223b3 Merge remote-tracking branch 'origin/unstable' into testing --- diff --git a/Makefile b/Makefile index 330bc4fca..8c15c1307 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ org.argeo.cms \ org.argeo.cms.ux \ org.argeo.cms.ee \ org.argeo.cms.lib.jetty \ +org.argeo.cms.lib.dbus \ org.argeo.cms.lib.sshd \ org.argeo.cms.cli \ osgi/equinox/org.argeo.cms.lib.equinox \ diff --git a/org.argeo.cms.lib.dbus/.classpath b/org.argeo.cms.lib.dbus/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/org.argeo.cms.lib.dbus/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.lib.dbus/.project b/org.argeo.cms.lib.dbus/.project new file mode 100644 index 000000000..bf0398c12 --- /dev/null +++ b/org.argeo.cms.lib.dbus/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.lib.dbus + + + + + + 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.dbus/OSGI-INF/cmsDBus.xml b/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml new file mode 100644 index 000000000..6797c3d9b --- /dev/null +++ b/org.argeo.cms.lib.dbus/OSGI-INF/cmsDBus.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.argeo.cms.lib.dbus/bnd.bnd b/org.argeo.cms.lib.dbus/bnd.bnd new file mode 100644 index 000000000..407c78525 --- /dev/null +++ b/org.argeo.cms.lib.dbus/bnd.bnd @@ -0,0 +1,5 @@ +Import-Package: \ +org.freedesktop.dbus.connections.transports,\ +* + +Service-Component: OSGI-INF/cmsDBus.xml diff --git a/org.argeo.cms.lib.dbus/build.properties b/org.argeo.cms.lib.dbus/build.properties new file mode 100644 index 000000000..76d7c98d4 --- /dev/null +++ b/org.argeo.cms.lib.dbus/build.properties @@ -0,0 +1,5 @@ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/cmsDBus.xml +source.. = src/ +output.. = bin/ diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java new file mode 100644 index 000000000..df768226d --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBus.java @@ -0,0 +1,9 @@ +package org.argeo.cms.dbus; + +import org.freedesktop.dbus.connections.impl.DBusConnection; + +public interface CmsDBus { + final static String DBUS_SESSION_BUS_ADDRESS = "DBUS_SESSION_BUS_ADDRESS"; + + DBusConnection openSessionConnection(); +} diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java new file mode 100644 index 000000000..cf939648a --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/dbus/CmsDBusImpl.java @@ -0,0 +1,97 @@ +package org.argeo.cms.dbus; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsLog; +import org.freedesktop.dbus.bin.EmbeddedDBusDaemon; +import org.freedesktop.dbus.connections.BusAddress; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; +import org.freedesktop.dbus.exceptions.DBusException; + +public class CmsDBusImpl implements CmsDBus { + private final static CmsLog log = CmsLog.getLog(CmsDBusImpl.class); + + private BusAddress sessionBusAddress; + + private EmbeddedDBusDaemon dBusDaemon; + private Path dBusDaemonSocket; + + private CmsEventBus cmsEventBus; + + public void start() { + try { + final String envSessionBusAddress = System.getenv(DBUS_SESSION_BUS_ADDRESS); + if (envSessionBusAddress != null) { + sessionBusAddress = BusAddress.of(envSessionBusAddress); + + // !! We must first initialise a connection, otherwise there are classloader + // issues later on + try (DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress) + .withShared(false).build()) { + + } + log.debug(() -> "Found session DBus with address " + sessionBusAddress); + } else { + Path socketLocation = Paths.get(System.getProperty("user.home"), ".cache", "argeo", "bus"); + if (Files.exists(socketLocation)) + Files.delete(socketLocation); + else + Files.createDirectories(socketLocation.getParent()); + + String embeddedSessionBusAddress = "unix:path=" + socketLocation.toString(); + dBusDaemon = new EmbeddedDBusDaemon(embeddedSessionBusAddress + ",listen=true"); + dBusDaemon.startInBackgroundAndWait(30 * 1000); + dBusDaemonSocket = socketLocation; + + sessionBusAddress = BusAddress.of(embeddedSessionBusAddress); + try (DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress) + .withShared(false).build()) { + + } + log.debug(() -> "Started embedded session DBus with address " + sessionBusAddress); + + // TODO set environment variable? + } + } catch (DBusException | IOException e) { + throw new IllegalStateException("Cannot find a session bus", e); + } + } + + public void stop() { + if (dBusDaemon != null) { + try { + dBusDaemon.close(); + } catch (IOException e) { + log.error("Cannot close embedded DBus daemon", e); + } + try { + Files.delete(dBusDaemonSocket); + } catch (IOException e) { + log.error("Cannot delete DBus daemon socket " + dBusDaemonSocket, e); + } + } + } + + @Override + public DBusConnection openSessionConnection() { + try { + DBusConnection dBusConnection = DBusConnectionBuilder.forAddress(sessionBusAddress).withShared(false) + .build(); + // TODO track all connections? + return dBusConnection; + } catch (DBusException e) { + e.printStackTrace(); + throw new IllegalStateException("Cannot open connection to session DBus", e); + } + } + + public void setCmsEventBus(CmsEventBus cmsEventBus) { + this.cmsEventBus = cmsEventBus; + } + +} diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java new file mode 100644 index 000000000..708ee6163 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/FreeDesktopApplication.java @@ -0,0 +1,26 @@ +package org.argeo.cms.freedesktop; + +import java.util.List; +import java.util.Map; + +import org.freedesktop.dbus.annotations.DBusInterfaceName; +import org.freedesktop.dbus.annotations.DBusMemberName; +import org.freedesktop.dbus.interfaces.DBusInterface; +import org.freedesktop.dbus.types.Variant; + +/** + * The org.freedesktop.Application interface. + */ +@DBusInterfaceName("org.freedesktop.Application") +public interface FreeDesktopApplication extends DBusInterface { + + @DBusMemberName(value = "Activate") + void activate(Map> platformData); + + @DBusMemberName(value = "Open") + void open(List uris, Map> platformData); + + @DBusMemberName(value = "ActivateAction") + void activateAction(String actionName, List> parameter, Map> platformData); + +} \ No newline at end of file diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd new file mode 100644 index 000000000..ba263d326 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/introspect-1.0.dtd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml new file mode 100644 index 000000000..5af11f722 --- /dev/null +++ b/org.argeo.cms.lib.dbus/src/org/argeo/cms/freedesktop/org.freedesktop.Application.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/OSGI-INF/cmsEventBus.xml b/org.argeo.cms/OSGI-INF/cmsEventBus.xml index 6bb67ceed..2c4a76698 100644 --- a/org.argeo.cms/OSGI-INF/cmsEventBus.xml +++ b/org.argeo.cms/OSGI-INF/cmsEventBus.xml @@ -1,6 +1,6 @@ - + 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 43cae8572..5920c4203 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 @@ -90,7 +90,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { } else { // TODO should we support prefixed name for known types? - QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString()); + QName providerName = provider.fromFsPrefixedName(path.getFileName().toString()); // QName providerName = new QName(path.getFileName().toString()); // TODO remove extension if mounted? this.name = new ContentName(providerName, session); @@ -126,7 +126,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { } else { UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); - String prefixedName = NamespaceUtils.toPrefixedName(provider, key); + String prefixedName = provider.toFsPrefixedName(key); if (!udfav.list().contains(prefixedName)) return Optional.empty(); ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName)); @@ -198,7 +198,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { if (udfav != null) { try { for (String name : udfav.list()) { - QName providerName = NamespaceUtils.parsePrefixedName(provider, name); + QName providerName = provider.fromFsPrefixedName(name); if (providerName.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) continue; // skip prefix mapping QName sessionName = new ContentName(providerName, getSession()); @@ -215,7 +215,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { protected void removeAttr(QName key) { UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); try { - udfav.delete(NamespaceUtils.toPrefixedName(provider, key)); + udfav.delete(provider.toFsPrefixedName(key)); } catch (IOException e) { throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e); } @@ -239,7 +239,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class); ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8)); try { - udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb); + udfav.write(provider.toFsPrefixedName(key), bb); } catch (IOException e) { throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e); } @@ -250,7 +250,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { if (POSIX_KEYS.containsKey(key)) return POSIX_KEYS.get(key); else - return USER_ + NamespaceUtils.toPrefixedName(provider, key); + return USER_ + provider.toFsPrefixedName(key); } /* @@ -285,7 +285,7 @@ public class FsContent extends AbstractContent implements ProvidedContent { public Content add(QName name, QName... classes) { FsContent fsContent; try { - Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name)); + Path newPath = path.resolve(provider.toFsPrefixedName(name)); if (ContentName.contains(classes, DName.collection.qName())) Files.createDirectory(newPath); else 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 9b1b96683..47cd64af3 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,6 +13,8 @@ import java.util.Objects; import java.util.TreeMap; import java.util.stream.Collectors; +import javax.xml.namespace.QName; + import org.argeo.api.acr.ArgeoNamespace; import org.argeo.api.acr.ContentResourceException; import org.argeo.api.acr.NamespaceUtils; @@ -20,28 +22,36 @@ import org.argeo.api.acr.RuntimeNamespaceContext; import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.util.OS; /** Access a file system as a {@link ContentProvider}. */ public class FsContentProvider implements ContentProvider { - final static String XMLNS_ = "xmlns:"; protected String mountPath; protected Path rootPath; private NavigableMap prefixes = new TreeMap<>(); + private final boolean isNtfs; + private final String XMLNS_; + public FsContentProvider(String mountPath, Path rootPath) { Objects.requireNonNull(mountPath); Objects.requireNonNull(rootPath); this.mountPath = mountPath; this.rootPath = rootPath; + + this.isNtfs = OS.LOCAL.isMSWindows(); + this.XMLNS_ = isNtfs ? "xmlns%3A" : "xmlns:"; + // FIXME make it more robust initNamespaces(); } protected FsContentProvider() { - + this.isNtfs = OS.LOCAL.isMSWindows(); + this.XMLNS_ = isNtfs ? "xmlns%3A" : "xmlns:"; } protected void initNamespaces() { @@ -117,6 +127,27 @@ public class FsContentProvider implements ContentProvider { return Files.exists(rootPath.resolve(relativePath)); } + /* + * ATTRIBUTE NAMES + */ + /** + * Make sure that the prefixed name is compatible with the underlying file + * system for file names/attributes (NTFS does not accept :) + */ + String toFsPrefixedName(QName key) { + return isNtfs ? NamespaceUtils.toPrefixedName(this, key).replace(":", "%3A") + : NamespaceUtils.toPrefixedName(this, key); + } + + /** + * PArse a prefixed name which is compatible with the underlying file system for + * file names/attributes (NTFS does not accept :) + */ + QName fromFsPrefixedName(String name) { + return isNtfs ? NamespaceUtils.parsePrefixedName(this, name.replace("%3A", ":")) + : NamespaceUtils.parsePrefixedName(this, name); + } + /* * NAMESPACE CONTEXT */ 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 289f8dcc6..379920724 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -45,6 +45,7 @@ class CmsAuthUtils { final static String SHARED_STATE_CERTIFICATE_CHAIN = "org.argeo.cms.auth.certificateChain"; final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr"; final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port"; + final static String SHARED_STATE_OS_USERNAME = "org.argeo.cms.os.username"; final static String SINGLE_USER_LOCAL_ID = "single-user"; 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 4b36f28ab..10e091ead 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java @@ -7,6 +7,7 @@ import javax.naming.ldap.LdapName; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.kerberos.KerberosPrincipal; +import javax.security.auth.login.CredentialException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import javax.security.auth.x500.X500Principal; @@ -35,8 +36,12 @@ public class SingleUserLoginModule implements LoginModule { @Override public boolean login() throws LoginException { String username = System.getProperty("user.name"); - if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) - sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username); + if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME) + && !username.equals(sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME))) + throw new CredentialException( + "OS username already set with " + sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME)); + if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME)) + sharedState.put(CmsAuthUtils.SHARED_STATE_OS_USERNAME, username); return true; } @@ -49,7 +54,7 @@ public class SingleUserLoginModule implements LoginModule { X500Principal principal = new X500Principal(userDn.toString()); authorizationName = principal.getName(); } else { - Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); + Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME); if (username == null) throw new LoginException("No username available"); String hostname = CmsContextImpl.getCmsContext().getCmsState().getHostname(); 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 2b5c41ddf..47b36f446 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -103,6 +103,11 @@ public class UserAdminLoginModule implements LoginModule { username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); password = null; preauth = true; + } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME)) { + // single user, we assume Kerberos or other mean for commit + username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_OS_USERNAME); + password = null; + preauth = true; } else { // ask for username and password @@ -205,7 +210,7 @@ public class UserAdminLoginModule implements LoginModule { // } UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin(); Authorization authorization; - if (callbackHandler == null) {// anonymous + if (callbackHandler == null && !sharedState.containsKey(CmsAuthUtils.SHARED_STATE_OS_USERNAME)) {// anonymous authorization = userAdmin.getAuthorization(null); } else if (bindAuthorization != null) {// bind authorization = bindAuthorization; @@ -277,7 +282,7 @@ public class UserAdminLoginModule implements LoginModule { if (log.isDebugEnabled()) { StringBuilder msg = new StringBuilder(); - msg.append("Logged in to CMS: " + authorization.getName() + "(" + authorization + ")\n"); + msg.append("Logged in to CMS: '" + authorization + "' (" + authorization.getName() + ")\n"); for (Principal principal : subject.getPrincipals()) { msg.append(" Principal: " + principal.getName()).append(" (") .append(principal.getClass().getSimpleName()).append(")\n"); 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 9d9e9bcfa..07af1b849 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,7 +10,7 @@ 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}. */ +/** An asynchronous {@link CmsEventBus} implementation based on {@link Flow}. */ public class CmsEventBusImpl implements CmsEventBus { private final static CmsLog log = CmsLog.getLog(CmsEventBus.class); 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 d364620f5..c1f92deb4 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 @@ -39,6 +39,7 @@ import org.argeo.api.uuid.UuidFactory; import org.argeo.cms.CmsDeployProperty; import org.argeo.cms.auth.ident.IdentClient; import org.argeo.cms.util.FsUtils; +import org.argeo.cms.util.OS; /** * Implementation of a {@link CmsState}, initialising the required services. @@ -168,7 +169,8 @@ public class CmsStateImpl implements CmsState { try { if (!Files.exists(privateDir)) Files.createDirectories(privateDir); - Files.setPosixFilePermissions(privateDir, posixPermissions); + if (!OS.LOCAL.isMSWindows()) + Files.setPosixFilePermissions(privateDir, posixPermissions); } catch (IOException e) { log.error("Cannot set permissions on " + privateDir, e); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsSynchronousEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsSynchronousEventBusImpl.java new file mode 100644 index 000000000..f7db37433 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsSynchronousEventBusImpl.java @@ -0,0 +1,76 @@ +package org.argeo.cms.internal.runtime; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.argeo.api.cms.CmsEventBus; +import org.argeo.api.cms.CmsEventSubscriber; +import org.argeo.api.cms.CmsLog; + +/** A simple synchronous {@link CmsEventBus} implementation. */ +public class CmsSynchronousEventBusImpl implements CmsEventBus { + private final static CmsLog log = CmsLog.getLog(CmsSynchronousEventBusImpl.class); + + private final Map> subscribers; + + public CmsSynchronousEventBusImpl() { + subscribers = Collections.synchronizedMap(new HashMap<>()); + } + + @Override + public void sendEvent(String topic, Map event) { + List subscribersOfTopic = subscribers.get(topic); + if (subscribersOfTopic == null) // no one cares + return; + synchronized (subscribersOfTopic) { + for (Iterator it = subscribersOfTopic.iterator(); it.hasNext();) { + CmsEventSubscriber subscriber = it.next(); + try { + subscriber.onEvent(topic, event); + } catch (Throwable e) { + log.error("Cannot process in topic " + topic + " the event " + event + " for subscriber " + + subscriber, e); + } + } + } + log.trace(() -> "Dispatched event in topic " + topic + ": " + event); + } + + @Override + public synchronized void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber) { + if (!subscribers.containsKey(topic)) { + subscribers.put(topic, new ArrayList<>()); + } + subscribers.get(topic).add(eventSubscriber); + log.debug(() -> "Added subscriber " + eventSubscriber + " to topic " + topic); + } + + @Override + public synchronized void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber) { + List subscribersOfTopic = subscribers.get(topic); + assert subscribersOfTopic != null; + if (subscribersOfTopic != null) { + CmsEventSubscriber removedSubscriber = null; + synchronized (subscribersOfTopic) { + subscribersOfTopic: for (Iterator it = subscribersOfTopic.iterator(); it + .hasNext();) { + CmsEventSubscriber subscriber = it.next(); + if (subscriber == eventSubscriber) { + it.remove(); + removedSubscriber = subscriber; + log.debug(() -> "Removed subscriber " + eventSubscriber + " from topic " + topic); + break subscribersOfTopic; + } + } + } + if (removedSubscriber == null) + log.warn(() -> "Subscriber " + eventSubscriber + " not found (and therefore not removed) in topic " + + topic); + } + } + +} 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 0fd0a63ed..bb1f6112a 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 @@ -9,9 +9,10 @@ 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; +import org.argeo.cms.util.OS; public class DeployedContentRepository extends CmsContentRepository { - private final static String ROOT_XML = "cr:root.xml"; + private final static String ROOT_XML = OS.LOCAL.isMSWindows() ? "cr%3Aroot.xml" : "cr:root.xml"; private final static CmsLog log = CmsLog.getLog(DeployedContentRepository.class); @@ -22,7 +23,8 @@ public class DeployedContentRepository extends CmsContentRepository { long begin = System.currentTimeMillis(); try { super.start(); - Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML); + // FIXME does not work on Windows + //Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML); initRootContentProvider(null); // Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg index 51db582c6..0ef142f4a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg @@ -32,7 +32,8 @@ SINGLE_USER { com.sun.security.auth.module.Krb5LoginModule optional storeKey=true useTicketCache=true; - org.argeo.cms.auth.SingleUserLoginModule requisite; + org.argeo.cms.auth.SingleUserLoginModule required; + org.argeo.cms.auth.UserAdminLoginModule optional; }; Jackrabbit { 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 index f71878060..6cf4ebf5e 100644 --- a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java @@ -22,7 +22,7 @@ public class OsUserUtils { public static LoginContext loginAsSystemUser(Subject subject) { try { URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader() - .getResource("org/argeo/osgi/useradmin/jaas-os.cfg"); + .getResource("org/argeo/cms/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, diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml new file mode 100644 index 000000000..e8bfefb3f --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDBusLauncher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd index 5cf5164e1..e1735f03c 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd +++ b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd @@ -5,9 +5,11 @@ org.argeo.cms.auth,\ org.eclipse.swt,\ org.eclipse.swt.graphics,\ org.w3c.css.sac,\ +org.freedesktop.dbus.connections,\ * Service-Component:\ OSGI-INF/cmsRcpDisplayFactory.xml,\ -OSGI-INF/cmsRcpHttpLauncher.xml +OSGI-INF/cmsRcpDBusLauncher.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 4ed1d4748..f85c0d8ca 100644 --- a/swt/rcp/org.argeo.cms.swt.rcp/build.properties +++ b/swt/rcp/org.argeo.cms.swt.rcp/build.properties @@ -1,6 +1,8 @@ -output.. = bin/ bin.includes = META-INF/,\ .,\ OSGI-INF/,\ - OSGI-INF/cmsRcpHttpLauncher.xml + OSGI-INF/cmsRcpHttpLauncher.xml,\ + OSGI-INF/cmsRcpDBusLauncher.xml +additional.bundles = org.slf4j.api source.. = src/ +output.. = bin/ 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 a83a54db3..63a1fd84e 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 @@ -67,7 +67,8 @@ public class CmsRcpDisplayFactory { display = null; } catch (UnsatisfiedLinkError e) { logger.log(Level.ERROR, - "Cannot load SWT, probably because the OSGi framework has been refresh. Restart the application.", + "Cannot load SWT, either because the SWT DLLs are no in the java.library.path," + + " or because the OSGi framework has been refreshed." + " Restart the application.", e); } } diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java new file mode 100644 index 000000000..ea6905757 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpDBusLauncher.java @@ -0,0 +1,48 @@ +package org.argeo.cms.ui.rcp.dbus; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.argeo.api.cms.CmsApp; +import org.argeo.cms.dbus.CmsDBus; + +public class CmsRcpDBusLauncher { + private CompletableFuture cmsDBus = new CompletableFuture<>(); + + private Map apps = new HashMap<>(); + + public void start() { + + } + + public void stop() { + + } + + public void addCmsApp(CmsApp cmsApp, Map properties) { + final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + cmsDBus.thenAcceptAsync((cmsDBus) -> { + CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsDBus, contextName, cmsApp); + apps.put(contextName, application); + }); + } + + public void removeCmsApp(CmsApp cmsApp, Map properties) { + final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY); + CmsRcpFreeDesktopApplication application = apps.remove(contextName); + if (application != null) { + try { + application.close(); + } catch (IOException e) { + throw new IllegalStateException("Cannot remove CMS RCP app " + contextName, e); + } + } + } + + public void setCmsDBus(CmsDBus cmsDBus) { + this.cmsDBus.complete(cmsDBus); + } + +} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java new file mode 100644 index 000000000..98c84fa41 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/CmsRcpFreeDesktopApplication.java @@ -0,0 +1,68 @@ +package org.argeo.cms.ui.rcp.dbus; + +import java.io.Closeable; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.argeo.api.cms.CmsApp; +import org.argeo.cms.dbus.CmsDBus; +import org.argeo.cms.freedesktop.FreeDesktopApplication; +import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.Variant; + +public class CmsRcpFreeDesktopApplication implements FreeDesktopApplication, Closeable { + private String path; + + private CmsApp cmsApp; + + private DBusConnection dBusConnection; + + public CmsRcpFreeDesktopApplication(CmsDBus cmsDBus, String contextName, CmsApp cmsApp) { + // TODO find a better prefix and/or make it customisable + this.path = "/org/argeo/cms/" + contextName; + this.cmsApp = cmsApp; + try { + String appName = path.replace('/', '.').substring(1); + dBusConnection = cmsDBus.openSessionConnection(); + dBusConnection.requestBusName(appName); + dBusConnection.exportObject(getObjectPath(), this); + } catch (DBusException e) { + throw new IllegalStateException("Cannot add CMS app " + path, e); + } + } + + @Override + public String getObjectPath() { + return path; + } + + @Override + public void close() throws IOException { + if (dBusConnection != null) + dBusConnection.close(); + } + + @Override + public void activate(Map> platformData) { + // String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : + // ""; + String uiName = "app"; + CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null); + } + + @Override + public void open(List uris, Map> platformData) { + // TODO Auto-generated method stub + + } + + @Override + public void activateAction(String actionName, List> parameter, Map> platformData) { + // TODO Auto-generated method stub + + } + +} diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java new file mode 100644 index 000000000..8f2da9861 --- /dev/null +++ b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/dbus/TestApplication.java @@ -0,0 +1,96 @@ +package org.argeo.cms.ui.rcp.dbus; + +import java.util.List; +import java.util.Map; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.argeo.cms.freedesktop.FreeDesktopApplication; +import org.freedesktop.dbus.connections.impl.DBusConnection; +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.Variant; + +public class TestApplication implements FreeDesktopApplication { + private DBusConnection dBusConnection; + + private final Display display; + + private Shell shell = null; + + private Text text; + + public TestApplication() throws DBusException { + display = Display.getCurrent(); + + /* Get a connection to the session bus so we can request a bus name */ + dBusConnection = DBusConnectionBuilder.forSessionBus().build(); +// m_conn = DBusConnectionBuilder.forAddress("unix:path=/tmp/dbus-80908265778467677465").build(); +// m_conn = DBusConnectionBuilder.forAddress("tcp:host=localhost,port=55556").build(); + /* Request a unique bus name */ + dBusConnection.requestBusName("org.argeo.TestApplication"); + /* Export this object onto the bus using the path '/' */ + dBusConnection.exportObject(getObjectPath(), this); + } + + @Override + public String getObjectPath() { + return "/org/argeo/TestApplication"; + } + + @Override + public void activate(Map> platformData) { + display.syncExec(() -> { + shellVisible(); + }); + + } + + protected void shellVisible() { + if (shell == null || shell.isDisposed()) { + shell = new Shell(display); + shell.setLayout(new GridLayout()); + text = new Text(shell, SWT.MULTI | SWT.WRAP); + text.setLayoutData(CmsSwtUtils.fillAll()); + text.setText("New shell\n"); + shell.open(); + } else { + } + shell.forceActive(); + } + + @Override + public void open(List uris, Map> platformData) { + display.syncExec(() -> { + shellVisible(); + for (String uri : uris) { + text.append(uri); + text.append("\n"); + } + shell.forceActive(); + }); + } + + @Override + public void activateAction(String actionName, List> parameter, Map> platformData) { + display.syncExec(() -> { + shellVisible(); + text.append("Execute action '" + actionName + "' with arguments " + parameter); + text.append("\n"); + }); + } + + public static void main(String[] args) throws DBusException { + Display display = new Display(); + new TestApplication(); + while (!display.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } +}