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 \
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.lib.dbus</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="cmsDBus">
+ <implementation class="org.argeo.cms.dbus.CmsDBusImpl"/>
+ <service>
+ <provide interface="org.argeo.cms.dbus.CmsDBus"/>
+ </service>
+ <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
+</scr:component>
--- /dev/null
+Import-Package: \
+org.freedesktop.dbus.connections.transports,\
+*
+
+Service-Component: OSGI-INF/cmsDBus.xml
--- /dev/null
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/cmsDBus.xml
+source.. = src/
+output.. = bin/
--- /dev/null
+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();
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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<String, Variant<?>> platformData);
+
+ @DBusMemberName(value = "Open")
+ void open(List<String> uris, Map<String, Variant<?>> platformData);
+
+ @DBusMemberName(value = "ActivateAction")
+ void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData);
+
+}
\ No newline at end of file
--- /dev/null
+<!-- DTD for D-Bus Introspection data -->
+<!-- (C) 2005-02-02 David A. Wheeler; released under the D-Bus licenses,
+ GNU GPL version 2 (or greater) and AFL 1.1 (or greater) -->
+
+<!-- see D-Bus specification for documentation -->
+
+<!ELEMENT node (node|interface)*>
+<!ATTLIST node name CDATA #IMPLIED>
+
+<!ELEMENT interface (method|signal|property|annotation)*>
+<!ATTLIST interface name CDATA #REQUIRED>
+
+<!ELEMENT method (arg|annotation)*>
+<!ATTLIST method name CDATA #REQUIRED>
+
+<!ELEMENT signal (arg|annotation)*>
+<!ATTLIST signal name CDATA #REQUIRED>
+
+<!ELEMENT arg EMPTY>
+<!ATTLIST arg name CDATA #IMPLIED>
+<!ATTLIST arg type CDATA #REQUIRED>
+<!-- Method arguments SHOULD include "direction",
+ while signal and error arguments SHOULD not (since there's no point).
+ The DTD format can't express that subtlety. -->
+<!ATTLIST arg direction (in|out) "in">
+
+<!-- AKA "attribute" -->
+<!ELEMENT property (annotation)*>
+<!ATTLIST property name CDATA #REQUIRED>
+<!ATTLIST property type CDATA #REQUIRED>
+<!ATTLIST property access (read|write|readwrite) #REQUIRED>
+
+<!ELEMENT annotation EMPTY> <!-- Generic metadata -->
+<!ATTLIST annotation name CDATA #REQUIRED>
+<!ATTLIST annotation value CDATA #REQUIRED>
+
+
--- /dev/null
+<!DOCTYPE node SYSTEM "introspect-1.0.dtd">
+<!-- See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus -->
+<node>
+ <interface name="org.freedesktop.Application">
+ <method name="Activate">
+ <arg type="a{sv}" name="platform-data" direction="in" />
+ </method>
+ <method name="Open">
+ <arg type="as" name="uris" direction="in" />
+ <arg type="a{sv}" name="platform-data" direction="in" />
+ </method>
+ <method name="ActivateAction">
+ <arg type="s" name="action-name" direction="in" />
+ <arg type="av" name="parameter" direction="in" />
+ <arg type="a{sv}" name="platform-data" direction="in" />
+ </method>
+ </interface>
+</node>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="CMS Event Bus">
- <implementation class="org.argeo.cms.internal.runtime.CmsEventBusImpl"/>
+ <implementation class="org.argeo.cms.internal.runtime.CmsSynchronousEventBusImpl"/>
<service>
<provide interface="org.argeo.api.cms.CmsEventBus"/>
</service>
} 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);
} 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));
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());
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);
}
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);
}
if (POSIX_KEYS.containsKey(key))
return POSIX_KEYS.get(key);
else
- return USER_ + NamespaceUtils.toPrefixedName(provider, key);
+ return USER_ + provider.toFsPrefixedName(key);
}
/*
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
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;
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<String, String> 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() {
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
*/
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";
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;
@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;
}
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();
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
// }
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;
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");
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);
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.
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);
}
--- /dev/null
+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<String, List<CmsEventSubscriber>> subscribers;
+
+ public CmsSynchronousEventBusImpl() {
+ subscribers = Collections.synchronizedMap(new HashMap<>());
+ }
+
+ @Override
+ public void sendEvent(String topic, Map<String, Object> event) {
+ List<CmsEventSubscriber> subscribersOfTopic = subscribers.get(topic);
+ if (subscribersOfTopic == null) // no one cares
+ return;
+ synchronized (subscribersOfTopic) {
+ for (Iterator<CmsEventSubscriber> 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<CmsEventSubscriber> subscribersOfTopic = subscribers.get(topic);
+ assert subscribersOfTopic != null;
+ if (subscribersOfTopic != null) {
+ CmsEventSubscriber removedSubscriber = null;
+ synchronized (subscribersOfTopic) {
+ subscribersOfTopic: for (Iterator<CmsEventSubscriber> 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);
+ }
+ }
+
+}
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);
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);
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 {
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,
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="cmsRcpDBusLauncher">
+ <implementation class="org.argeo.cms.ui.rcp.dbus.CmsRcpDBusLauncher"/>
+ <reference bind="setCmsDBus" cardinality="1..1" interface="org.argeo.cms.dbus.CmsDBus" name="CmsDBus" policy="static"/>
+ <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+</scr:component>
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
-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/
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);
}
}
--- /dev/null
+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> cmsDBus = new CompletableFuture<>();
+
+ private Map<String, CmsRcpFreeDesktopApplication> apps = new HashMap<>();
+
+ public void start() {
+
+ }
+
+ public void stop() {
+
+ }
+
+ public void addCmsApp(CmsApp cmsApp, Map<String, String> 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<String, String> 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);
+ }
+
+}
--- /dev/null
+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<String, Variant<?>> platformData) {
+ // String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) :
+ // "";
+ String uiName = "app";
+ CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+ }
+
+ @Override
+ public void open(List<String> uris, Map<String, Variant<?>> platformData) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> platformData) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
--- /dev/null
+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<String, Variant<?>> 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<String> uris, Map<String, Variant<?>> platformData) {
+ display.syncExec(() -> {
+ shellVisible();
+ for (String uri : uris) {
+ text.append(uri);
+ text.append("\n");
+ }
+ shell.forceActive();
+ });
+ }
+
+ @Override
+ public void activateAction(String actionName, List<Variant<?>> parameter, Map<String, Variant<?>> 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();
+ }
+}