Merge remote-tracking branch 'origin/v2.x'
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 10 Nov 2021 06:11:38 +0000 (07:11 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 10 Nov 2021 06:11:38 +0000 (07:11 +0100)
78 files changed:
.gitignore
cnf/build.bnd [new file with mode: 0644]
demo/cms-cluster_0.properties
demo/cms-cluster_1.properties
demo/pom.xml
dep/org.argeo.dep.cms.ext/.gitignore [new file with mode: 0644]
dep/org.argeo.dep.cms.ext/META-INF/.gitignore [new file with mode: 0644]
dep/org.argeo.dep.cms.ext/bnd.bnd [new file with mode: 0644]
dep/org.argeo.dep.cms.ext/build.properties [new file with mode: 0644]
dep/org.argeo.dep.cms.ext/p2.inf [new file with mode: 0644]
dep/org.argeo.dep.cms.ext/pom.xml [new file with mode: 0644]
dep/org.argeo.dep.cms.sdk/pom.xml
dep/pom.xml
dist/argeo-node/base/etc/argeo.d/jvm.args.debug [new file with mode: 0644]
dist/argeo-node/pom.xml
dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service
org.argeo.api/.classpath
org.argeo.api/src/org/argeo/api/PublishNamespace.java [new file with mode: 0644]
org.argeo.cms.e4.rap/.classpath
org.argeo.cms.e4/.classpath
org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java
org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java
org.argeo.cms.ui.theme/.classpath
org.argeo.cms.ui/.classpath
org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java
org.argeo.cms/.classpath
org.argeo.cms/OSGI-INF/filesServlet.xml
org.argeo.cms/OSGI-INF/pkgServlet.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/pkgServletContext.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/SingleUserAuthorization.java
org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java
org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml
org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java
org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java
org.argeo.core/.classpath
org.argeo.eclipse.ui.rap/.classpath
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
org.argeo.eclipse.ui/.classpath
org.argeo.enterprise/.classpath
org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java
org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java
org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java [new file with mode: 0644]
org.argeo.jcr/.classpath
org.argeo.jcr/src/org/argeo/jcr/Jcr.java
org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java
org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java
org.argeo.maintenance/.classpath
org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java
org.argeo.osgi.boot/.classpath

index b83d22266ac8aa2f8df2edef68082c789727841d..bcf25b2aca450dc41645b798b4fd74f07a421d4e 100644 (file)
@@ -1 +1,2 @@
 /target/
+*/generated
diff --git a/cnf/build.bnd b/cnf/build.bnd
new file mode 100644 (file)
index 0000000..150e1d4
--- /dev/null
@@ -0,0 +1,18 @@
+category: org.argeo.commons
+version: 2.1.104
+buildId: S
+#buildId: r${tstamp}
+
+Bundle-Version: 2.1.104.S
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+
+Private-Package: org.argeo.*.internal.*
+Export-Package: !org.argeo.*.internal.*; org.argeo.*
+SLC-Category: ${category}
+#SLC-Build-Timestamp: ${tstamp}
+-savemanifest = META-INF/MANIFEST.MF
+-removeheaders = Bnd-LastModified,Build-Jdk,Built-By,Tool,Created-By
+-groupId = ${category}
+Automatic-Module-Name: ${bsn}
+
+
index c0bb9da2b4bb2aaacd9e6c1490a7dd7ca9a757ee..d0c3fb2f88d0e839038211128a111a629ef5157d 100644 (file)
@@ -13,7 +13,7 @@ org.argeo.cms.e4.rap
 
 # Local
 org.osgi.service.http.port=7070
-argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap:10389/dc=example,dc=com
+argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
 argeo.node.repo.type=postgresql_cluster_ds
 argeo.node.repo.clusterId=03233754-16c3-49a1-8a00-58bf89a65182
 argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster
index cf48eab020821b32912916e0a7ccd46ef2feaeb4..b5e60f85b2f61ff28fa83932814395db15cf4559 100644 (file)
@@ -13,7 +13,7 @@ org.argeo.cms.e4.rap
 
 # Local
 org.osgi.service.http.port=7071
-argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap:10389/dc=example,dc=com
+argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
 argeo.node.repo.type=postgresql_cluster_ds
 argeo.node.repo.clusterId=52463fa3-2917-4814-9ff7-685c41cbc7c7
 argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster
index 40855213e94199d519099747b8f628bed81de88d..1716fa8533e13459a295c7fda3c418c323bd2071 100644 (file)
@@ -41,7 +41,7 @@
                        <dependencies>
                                <dependency>
                                        <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.sdk</artifactId>
+                                       <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
                                        <version>2.3.1-SNAPSHOT</version>
                                </dependency>
                        </dependencies>
diff --git a/dep/org.argeo.dep.cms.ext/.gitignore b/dep/org.argeo.dep.cms.ext/.gitignore
new file mode 100644 (file)
index 0000000..e26e09f
--- /dev/null
@@ -0,0 +1,4 @@
+/target/
+/feature.xml
+/modularDistribution.csv
+/*-maven.target
diff --git a/dep/org.argeo.dep.cms.ext/META-INF/.gitignore b/dep/org.argeo.dep.cms.ext/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/dep/org.argeo.dep.cms.ext/bnd.bnd b/dep/org.argeo.dep.cms.ext/bnd.bnd
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/dep/org.argeo.dep.cms.ext/build.properties b/dep/org.argeo.dep.cms.ext/build.properties
new file mode 100644 (file)
index 0000000..edef3d9
--- /dev/null
@@ -0,0 +1,2 @@
+bin.includes = feature.xml,\
+               modularDistribution.csv
diff --git a/dep/org.argeo.dep.cms.ext/p2.inf b/dep/org.argeo.dep.cms.ext/p2.inf
new file mode 100644 (file)
index 0000000..0423aa5
--- /dev/null
@@ -0,0 +1,2 @@
+properties.1.name=org.eclipse.equinox.p2.type.category
+properties.1.value=true
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.ext/pom.xml b/dep/org.argeo.dep.cms.ext/pom.xml
new file mode 100644 (file)
index 0000000..19b5b48
--- /dev/null
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <version>2.3.1-SNAPSHOT</version>
+               <artifactId>dep</artifactId>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.dep.cms.ext</artifactId>
+       <name>CMS Optional Third Parties</name>
+       <description>Bulky generic third parties which are not required by the CMS, but necessary for upper layers.</description>
+       <dependencies>
+               <!-- Additional Third Parties -->
+               <dependency>
+                       <groupId>org.argeo.tp.javax</groupId>
+                       <artifactId>javax.xml.bind</artifactId>
+               </dependency>
+
+               <!-- Jackson JSON processor -->
+               <dependency>
+                       <groupId>org.argeo.tp.jackson</groupId>
+                       <artifactId>com.fasterxml.jackson.core.jackson-core</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.jackson</groupId>
+                       <artifactId>com.fasterxml.jackson.core.jackson-databind</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.jackson</groupId>
+                       <artifactId>com.fasterxml.jackson.core.jackson-annotations</artifactId>
+               </dependency>
+
+               <!-- Mail -->
+               <dependency>
+                       <groupId>org.argeo.tp.javax</groupId>
+                       <artifactId>javax.activation</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.javax</groupId>
+                       <artifactId>javax.mail</artifactId>
+               </dependency>
+
+               <!-- POI requirements -->
+               <dependency>
+                       <groupId>org.argeo.tp.apache.commons</groupId>
+                       <artifactId>org.apache.commons.math3</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache.commons</groupId>
+                       <artifactId>org.apache.commons.collections4</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache</groupId>
+                       <artifactId>org.apache.xml.security</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache</groupId>
+                       <artifactId>org.apache.xmlbeans</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache</groupId>
+                       <artifactId>org.apache.xalan</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache</groupId>
+                       <artifactId>org.apache.xalan.serializer</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache</groupId>
+                       <artifactId>org.apache.xml.resolver</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.apache</groupId>
+                       <artifactId>org.apache.xerces</artifactId>
+               </dependency>
+
+       </dependencies>
+
+       <profiles>
+               <profile>
+                       <id>rpmbuild-tp</id>
+                       <build>
+                               <plugins>
+                                       <plugin>
+                                               <artifactId>maven-assembly-plugin</artifactId>
+                                               <executions>
+                                                       <execution>
+                                                               <id>prepare-source-tp</id>
+                                                               <phase>package</phase>
+                                                               <goals>
+                                                                       <goal>single</goal>
+                                                               </goals>
+                                                               <configuration>
+                                                                       <descriptorRefs>
+                                                                               <descriptorRef>a2-source-tp</descriptorRef>
+                                                                       </descriptorRefs>
+                                                               </configuration>
+                                                       </execution>
+                                               </executions>
+                                       </plugin>
+                                       <plugin>
+                                               <groupId>org.codehaus.mojo</groupId>
+                                               <artifactId>rpm-maven-plugin</artifactId>
+                                               <executions>
+                                                       <execution>
+                                                               <id>rpm-tp</id>
+                                                               <phase>package</phase>
+                                                               <goals>
+                                                                       <goal>rpm</goal>
+                                                               </goals>
+                                                               <configuration>
+                                                                       <name>argeo-cms-ext-tp</name>
+                                                                       <projversion>${version.argeo-tp}</projversion>
+                                                                       <release>${maven.build.timestamp}</release>
+                                                                       <mappings>
+                                                                               <mapping>
+                                                                                       <directory>/usr/share/osgi</directory>
+                                                                                       <username>root</username>
+                                                                                       <groupname>root</groupname>
+                                                                                       <filemode>644</filemode>
+                                                                                       <directoryIncluded>false</directoryIncluded>
+                                                                                       <sources>
+                                                                                               <source>
+                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp</location>
+                                                                                                       <includes>
+                                                                                                               <include>**/*.jar</include>
+                                                                                                       </includes>
+                                                                                               </source>
+                                                                                       </sources>
+                                                                               </mapping>
+                                                                       </mappings>
+                                                                       <requires>
+                                                                               <require>argeo-cms-node-tp</require>
+                                                                       </requires>
+                                                               </configuration>
+                                                       </execution>
+                                               </executions>
+                                       </plugin>
+                               </plugins>
+                       </build>
+               </profile>
+       </profiles>
+</project>
\ No newline at end of file
index d752f9850dac848e99040fa4084ca9da47e67922..1029790a2e6b4d71a94132c404134fff131f827c 100644 (file)
@@ -3,7 +3,7 @@
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>org.argeo.commons</groupId>
-               <version>2.3.1-SNAPSHOT</version>
+               <version>2.1.104-SNAPSHOT</version>
                <artifactId>dep</artifactId>
                <relativePath>..</relativePath>
        </parent>
@@ -13,8 +13,8 @@
                <!-- Parent dependencies -->
                <dependency>
                        <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
-                       <version>2.3.1-SNAPSHOT</version>
+                       <artifactId>org.argeo.dep.cms.ui.rap</artifactId>
+                       <version>2.1.104-SNAPSHOT</version>
                        <type>pom</type>
                </dependency>
 
@@ -23,7 +23,7 @@
                <dependency>
                        <groupId>org.argeo.commons</groupId>
                        <artifactId>org.argeo.osgi.boot</artifactId>
-                       <version>2.3.1-SNAPSHOT</version>
+                       <version>2.1.104-SNAPSHOT</version>
                        <scope>test</scope>
                </dependency>
 
                                                                                </mapping>
                                                                        </mappings>
                                                                        <requires>
-                                                                               <require>argeo-cms-platform-tp</require>
                                                                        </requires>
                                                                </configuration>
                                                        </execution>
index 0d0c750ae99a490bb8d6dd4ed47e0ff5ae89d95f..37e847f05211fee0697bbe68623d0feb506a5444 100644 (file)
@@ -15,7 +15,7 @@
                <module>org.argeo.dep.cms.node</module>
                <module>org.argeo.dep.cms.ui.rap</module>
                <module>org.argeo.dep.cms.e4.rap</module>
-               <module>org.argeo.dep.cms.sdk</module>
+               <module>org.argeo.dep.cms.ext</module>
        </modules>
        <build>
                <plugins>
diff --git a/dist/argeo-node/base/etc/argeo.d/jvm.args.debug b/dist/argeo-node/base/etc/argeo.d/jvm.args.debug
new file mode 100644 (file)
index 0000000..4e6b1dc
--- /dev/null
@@ -0,0 +1 @@
+-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:8000
\ No newline at end of file
index b0f9aa2686846540a03245b4e978abbf2e620c31..f37d15d1db018d9050d251f59ca8eee1cd09112c 100644 (file)
                                        <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
                                        <version>2.3.1-SNAPSHOT</version>
                                </dependency>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.sdk</artifactId>
-                                       <version>2.3.1-SNAPSHOT</version>
-                               </dependency>
                                <dependency>
                                        <groupId>org.argeo.commons</groupId>
                                        <artifactId>osgi-boot</artifactId>
index b310048e15517e234c21b960bf20378ebc944238..c631825a27b3a5228b1ec53ce56807c24b68e3b2 100644 (file)
@@ -21,10 +21,10 @@ ExecStart=/usr/lib/jvm/jre-11/bin/java \
 -Dosgi.noShutdown=true \
 -Dorg.eclipse.equinox.http.jetty.autostart=false \
 -Dosgi.bundles=org.argeo.osgi.boot@start \
-@/usr/share/osgi/boot/framework.args \
 @/usr/share/argeo/jvm.args \
 @/etc/argeo.d/jvm.args \
 @/etc/argeo.d/%I/jvm.args \
+@/usr/share/osgi/boot/framework.args \
 -configuration /var/lib/argeo.d/%I/state \
 -data /var/lib/argeo.d/%I/data
 # Exit codes of the JVM when SIGTERM or SIGINT have been caught:
index eca7bdba8f03f22510b7980a94dbfe10c16c0901..e801ebfb4680123285c15553dc70584276fe0057 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
diff --git a/org.argeo.api/src/org/argeo/api/PublishNamespace.java b/org.argeo.api/src/org/argeo/api/PublishNamespace.java
new file mode 100644 (file)
index 0000000..ff95754
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.api;
+
+import org.osgi.resource.Namespace;
+
+/** Namespace defining which resources can be published. Typically use to expose icon of scripts to the web. */
+public class PublishNamespace extends Namespace {
+
+       public static final String CMS_PUBLISH_NAMESPACE = "cms.publish";
+       public static final String PKG = "pkg";
+       public static final String FILE = "file";
+
+       private PublishNamespace() {
+               // empty
+       }
+
+}
index eca7bdba8f03f22510b7980a94dbfe10c16c0901..e801ebfb4680123285c15553dc70584276fe0057 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index eca7bdba8f03f22510b7980a94dbfe10c16c0901..e801ebfb4680123285c15553dc70584276fe0057 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index 361e8a0d21778b04113ca896dd27c535769cee79..c1bd3ad9893c279b0cb9c4f6b715fc6c95916bb5 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.cms.web;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -13,12 +14,13 @@ import org.argeo.cms.ui.CmsView;
 import org.argeo.util.LangUtils;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
 import org.eclipse.rap.rwt.application.ApplicationConfiguration;
 import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
 import org.eclipse.rap.rwt.client.WebClient;
 import org.eclipse.swt.widgets.Display;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.event.EventAdmin;
 
@@ -28,6 +30,7 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
 
        private BundleContext bundleContext;
        private CmsApp cmsApp;
+       private String cmsAppId;
        private EventAdmin eventAdmin;
 
        private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
@@ -35,16 +38,22 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
        private final static String CONTEXT_NAME = "contextName";
        private String contextName;
 
+       private final static String FAVICON_PNG = "favicon.png";
+
        public void init(BundleContext bundleContext, Map<String, String> properties) {
                this.bundleContext = bundleContext;
                contextName = properties.get(CONTEXT_NAME);
-               if (cmsApp != null)
-                       themingUpdated();
+               if (cmsApp != null) {
+                       if (cmsApp.allThemesAvailable())
+                               publishWebApp();
+               }
        }
 
        public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-               if (cmsApp != null)
+               if (cmsApp != null) {
                        cmsApp.removeCmsAppListener(this);
+                       cmsApp = null;
+               }
        }
 
        @Override
@@ -84,6 +93,11 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
                        if (theme != null) {
                                properties.put(WebClient.THEME_ID, theme.getThemeId());
                                properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+                               properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
+                               Set<String> imagePaths = theme.getImagesPaths();
+                               if (imagePaths.contains(FAVICON_PNG)) {
+                                       properties.put(WebClient.FAVICON, FAVICON_PNG);
+                               }
                        } else {
                                properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
                        }
@@ -96,8 +110,8 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
                        if (log.isDebugEnabled())
                                log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
                }
-               if (log.isDebugEnabled())
-                       log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
+//             if (log.isDebugEnabled())
+//                     log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
        }
 
        CmsApp getCmsApp() {
@@ -110,10 +124,17 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
 
        public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
                this.cmsApp = cmsApp;
+               this.cmsAppId = properties.get(Constants.SERVICE_PID);
                this.cmsApp.addCmsAppListener(this);
        }
 
        public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String cmsAppId = properties.get(Constants.SERVICE_PID);
+               if (!cmsAppId.equals(this.cmsAppId))
+                       return;
+               if (this.cmsApp != null) {
+                       this.cmsApp.removeCmsAppListener(this);
+               }
                if (rwtAppReg != null)
                        rwtAppReg.unregister();
                this.cmsApp = null;
@@ -121,6 +142,11 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
 
        @Override
        public void themingUpdated() {
+               if (cmsApp != null && cmsApp.allThemesAvailable())
+                       publishWebApp();
+       }
+
+       protected void publishWebApp() {
                Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
                if (rwtAppReg != null)
                        rwtAppReg.unregister();
index 288069bd9de464400c934da1aab14d68a51bd760..b1691cb0536f0d725e5d8217bcb1c60c831b142f 100644 (file)
@@ -114,8 +114,8 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL
                                                Locale rwtLocale = RWT.getUISession().getLocale();
                                                LocaleUtils.setThreadLocale(rwtLocale);
                                        }
+                                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
                                        ui = cmsWebApp.getCmsApp().initUi(parent);
-                                       ui.setData(CmsApp.UI_NAME_PROPERTY, uiName);
                                        ui.setLayoutData(CmsUiUtils.fillAll());
                                        // we need ui to be set before refresh so that CmsView can store UI context data
                                        // in it.
index eca7bdba8f03f22510b7980a94dbfe10c16c0901..e801ebfb4680123285c15553dc70584276fe0057 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index eca7bdba8f03f22510b7980a94dbfe10c16c0901..e801ebfb4680123285c15553dc70584276fe0057 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index 2ecc658413507860381d30f238e7019a1dd29350..77cd9838305b1c14dfc2ace95a06b887a4e2b27b 100644 (file)
@@ -25,10 +25,13 @@ public abstract class AbstractCmsApp implements CmsApp {
                String themeId = getThemeId(uiName);
                if (themeId == null)
                        return null;
+               if (!themes.containsKey(themeId))
+                       throw new IllegalArgumentException("Theme " + themeId + " not found.");
                return themes.get(themeId);
        }
 
-       protected boolean allThemesAvailable() {
+       @Override
+       public boolean allThemesAvailable() {
                boolean themeMissing = false;
                uiNames: for (String uiName : getUiNames()) {
                        String themeId = getThemeId(uiName);
index 3e445ce7baae3d4cff00fb04b3519d92dcdb3a59..bd7b00334e223f0de673089598c1e98a63d3a737 100644 (file)
@@ -25,6 +25,8 @@ public interface CmsApp {
 
        CmsTheme getTheme(String uiName);
 
+       boolean allThemesAvailable();
+
        void addCmsAppListener(CmsAppListener listener);
 
        void removeCmsAppListener(CmsAppListener listener);
index 506e971ccb14191b131e9c315b0a13dd0ad509e3..678a497170f60cf90cd7ff7a1f3c5d6b2f6679f8 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.cms.ui;
 
 /** Styles references in the CSS. */
+@Deprecated
 public interface CmsStyles {
        // General
        public final static String CMS_SHELL = "cms_shell";
index e5089e35f65716ef02de5d15e4c73068f682f54c..c93991b274e7126bf609284015c01e574fc61a70 100644 (file)
@@ -37,6 +37,9 @@ public interface CmsTheme {
        /** Tags to be added to the header section of the HTML page. */
        String getHtmlHeaders();
 
+       /** The HTML body to use. */
+       String getBodyHtml();
+
        /** The image registered at this path, or <code>null</code> if not found. */
        Image getImage(String path);
 
index 5ec931e71920b66a9ee1071165eb267ffa80df86..c3fd7960d146296331bcae3daf91402736eed43c 100644 (file)
@@ -12,7 +12,7 @@ import org.eclipse.swt.widgets.Display;
 
 /** Centralises some generic {@link CmsTheme} patterns. */
 public abstract class AbstractCmsTheme implements CmsTheme {
-       private Map<String, Image> imageCache = new HashMap<>();
+       private Map<String, ImageData> imageCache = new HashMap<>();
 
        private Map<String, Map<Integer, String>> iconPaths = new HashMap<>();
 
@@ -24,13 +24,14 @@ public abstract class AbstractCmsTheme implements CmsTheme {
                                if (in == null)
                                        return null;
                                ImageData imageData = new ImageData(in);
-                               Image image = new Image(Display.getDefault(), imageData);
-                               imageCache.put(path, image);
+                               imageCache.put(path, imageData);
                        } catch (IOException e) {
                                throw new IllegalStateException(e);
                        }
                }
-               return imageCache.get(path);
+               ImageData imageData = imageCache.get(path);
+               Image image = new Image(Display.getCurrent(), imageData);
+               return image;
        }
 
        @Override
index 3b2669e02242e9f96c39238f5c4f804695933982..6b997e6a35c4f0e691b412b1d9129a2759b4ede7 100644 (file)
@@ -7,6 +7,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
@@ -15,6 +16,7 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 
+import org.apache.commons.io.IOUtils;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 
@@ -33,6 +35,10 @@ public class BundleCmsTheme extends AbstractCmsTheme {
        public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
        public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle";
 
+       private final static String HEADER_CSS = "header.css";
+       private final static String FONTS_TXT = "fonts.txt";
+       private final static String BODY_HTML = "body.html";
+
 //     private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
 
        private String themeId;
@@ -45,6 +51,8 @@ public class BundleCmsTheme extends AbstractCmsTheme {
        private String headerCss;
        private List<String> fonts = new ArrayList<>();
 
+       private String bodyHtml="<body></body>";
+
        private String basePath;
        private String styleCssPath;
 //     private String webCssPath;
@@ -103,21 +111,30 @@ public class BundleCmsTheme extends AbstractCmsTheme {
                addFonts("*.woff2");
 
                // fonts
-               URL fontsUrl = themeBundle.getEntry(basePath + "fonts.txt");
+               URL fontsUrl = themeBundle.getEntry(basePath + FONTS_TXT);
                if (fontsUrl != null) {
                        loadFontsUrl(fontsUrl);
                }
 
                // common CSS header (plain CSS)
-               URL headerCssUrl = themeBundle.getEntry(basePath + "header.css");
+               URL headerCssUrl = themeBundle.getEntry(basePath + HEADER_CSS);
                if (headerCssUrl != null) {
+                       // added to plain Web CSS
+                       webCssPaths.add(basePath + HEADER_CSS);
+                       // and it will also be used by RAP:
                        try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) {
                                headerCss = buffer.lines().collect(Collectors.joining("\n"));
                        } catch (IOException e) {
                                throw new IllegalArgumentException("Cannot read " + headerCssUrl, e);
                        }
                }
-       }
+
+               // body
+               URL bodyUrl = themeBundle.getEntry(basePath + BODY_HTML);
+               if (bodyUrl != null) {
+                       loadBodyHtml(bodyUrl);
+               }
+}
 
        public String getHtmlHeaders() {
                StringBuilder sb = new StringBuilder();
@@ -136,6 +153,13 @@ public class BundleCmsTheme extends AbstractCmsTheme {
                else
                        return sb.toString();
        }
+       
+       
+
+       @Override
+       public String getBodyHtml() {
+               return bodyHtml;
+       }
 
        Set<String> addCss(Bundle themeBundle, String path) {
                Set<String> paths = new TreeSet<>();
@@ -182,6 +206,14 @@ public class BundleCmsTheme extends AbstractCmsTheme {
                }
        }
 
+       void loadBodyHtml(URL url) {
+               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
+               bodyHtml=       IOUtils.toString(url,StandardCharsets.UTF_8);
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot load URL " + url, e);
+               }
+       }
+
        void addImages(String pattern) {
                Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
                if (themeResources == null)
index b3c4a8a579914f665469004d2ba4cafe9150e74a..4cad1de60fa74eb2f2fb487a6bd6dd008d5b7b63 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.cms.ui.util;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -7,14 +8,12 @@ import java.net.URL;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.api.NodeUtils;
-import org.argeo.cms.CmsException;
 import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.ui.CmsStyles;
 import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.JcrException;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.service.ResourceManager;
 import org.eclipse.swt.SWT;
@@ -32,9 +31,10 @@ public class CmsLink implements CmsUiProvider {
        private BundleContext bundleContext;
 
        private String label;
-       private String custom;
+       private String style;
        private String target;
        private String image;
+       private boolean openNew = false;
        private MouseListener mouseListener;
 
        private int horizontalAlignment = SWT.CENTER;
@@ -52,14 +52,18 @@ public class CmsLink implements CmsUiProvider {
        }
 
        public CmsLink(String label, String target) {
-               this(label, target, null);
+               this(label, target, (String) null);
        }
 
-       public CmsLink(String label, String target, String custom) {
+       public CmsLink(String label, String target, CmsStyle style) {
+               this(label, target, style != null ? style.style() : null);
+       }
+
+       public CmsLink(String label, String target, String style) {
                super();
                this.label = label;
                this.target = target;
-               this.custom = custom;
+               this.style = style;
                init();
        }
 
@@ -89,7 +93,7 @@ public class CmsLink implements CmsUiProvider {
                comp.setLayout(CmsUiUtils.noSpaceGridLayout());
 
                Label link = new Label(comp, SWT.NONE);
-               link.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
+               CmsUiUtils.markup(link);
                GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false);
                if (image != null) {
                        if (imageHeight != null)
@@ -100,13 +104,8 @@ public class CmsLink implements CmsUiProvider {
                }
 
                link.setLayoutData(layoutData);
-               if (custom != null) {
-                       comp.setData(RWT.CUSTOM_VARIANT, custom);
-                       link.setData(RWT.CUSTOM_VARIANT, custom);
-               } else {
-                       comp.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK);
-                       link.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_LINK);
-               }
+               CmsUiUtils.style(comp, style != null ? style : getDefaultStyle());
+               CmsUiUtils.style(link, style != null ? style : getDefaultStyle());
 
                // label
                StringBuilder labelText = new StringBuilder();
@@ -118,16 +117,19 @@ public class CmsLink implements CmsUiProvider {
                                        String homePath = homeNode.getPath();
                                        labelText.append("/#" + homePath);
                                } catch (RepositoryException e) {
-                                       throw new CmsException("Cannot get home path", e);
+                                       throw new JcrException("Cannot get home path", e);
                                }
                        } else {
                                labelText.append(loggedInTarget);
                        }
                        labelText.append("\">");
                } else if (target != null) {
-                       labelText.append("<a style='color:inherit;text-decoration:inherit;' href=\"");
-                       labelText.append(target);
-                       labelText.append("\">");
+                       labelText.append("<a style='color:inherit;text-decoration:inherit;' href='");
+                       labelText.append(target).append("'");
+                       if (openNew) {
+                               labelText.append(" target='_blank'");
+                       }
+                       labelText.append(">");
                }
                if (image != null) {
                        registerImageIfNeeded();
@@ -162,17 +164,12 @@ public class CmsLink implements CmsUiProvider {
                ResourceManager resourceManager = RWT.getResourceManager();
                if (!resourceManager.isRegistered(image)) {
                        URL res = getImageUrl();
-                       InputStream inputStream = null;
-                       try {
-                               IOUtils.closeQuietly(inputStream);
-                               inputStream = res.openStream();
+                       try (InputStream inputStream = res.openStream()) {
                                resourceManager.register(image, inputStream);
                                if (log.isTraceEnabled())
                                        log.trace("Registered image " + image);
-                       } catch (Exception e) {
-                               throw new CmsException("Cannot load image " + image, e);
-                       } finally {
-                               IOUtils.closeQuietly(inputStream);
+                       } catch (IOException e) {
+                               throw new RuntimeException("Cannot load image " + image, e);
                        }
                }
        }
@@ -180,16 +177,12 @@ public class CmsLink implements CmsUiProvider {
        private ImageData loadImage() {
                URL url = getImageUrl();
                ImageData result = null;
-               InputStream inputStream = null;
-               try {
-                       inputStream = url.openStream();
+               try (InputStream inputStream = url.openStream()) {
                        result = new ImageData(inputStream);
                        if (log.isTraceEnabled())
                                log.trace("Loaded image " + image);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot load image " + image, e);
-               } finally {
-                       IOUtils.closeQuietly(inputStream);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot load image " + image, e);
                }
                return result;
        }
@@ -204,7 +197,7 @@ public class CmsLink implements CmsUiProvider {
                }
 
                if (url == null)
-                       throw new CmsException("No image " + image + " available.");
+                       throw new IllegalStateException("No image " + image + " available.");
 
                return url;
        }
@@ -217,8 +210,14 @@ public class CmsLink implements CmsUiProvider {
                this.label = label;
        }
 
+       public void setStyle(String style) {
+               this.style = style;
+       }
+
+       /** @deprecated Use {@link #setStyle(String)} instead. */
+       @Deprecated
        public void setCustom(String custom) {
-               this.custom = custom;
+               this.style = custom;
        }
 
        public void setTarget(String target) {
@@ -255,7 +254,8 @@ public class CmsLink implements CmsUiProvider {
                } else if ("center".equals(vAlign)) {
                        verticalAlignment = SWT.CENTER;
                } else {
-                       throw new CmsException("Unsupported vertical allignment " + vAlign + " (must be: top, bottom or center)");
+                       throw new IllegalArgumentException(
+                                       "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)");
                }
        }
 
@@ -271,4 +271,11 @@ public class CmsLink implements CmsUiProvider {
                this.imageHeight = imageHeight;
        }
 
+       public void setOpenNew(boolean openNew) {
+               this.openNew = openNew;
+       }
+
+       protected String getDefaultStyle() {
+               return SimpleStyle.link.name();
+       }
 }
index ddbe485ea427ab4aed52bb14c376d16f7aad8614..ed2244b8ddce03e283df7b90f4d3a867a0529f54 100644 (file)
@@ -4,17 +4,19 @@ package org.argeo.cms.ui.util;
 public interface CmsStyle {
        String name();
 
+       /** @deprecated use {@link #style()} instead. */
        @Deprecated
        default String toStyleClass() {
-               return getClassPrefix() + "-" + name();
+               return style();
        }
 
        default String style() {
-               return getClassPrefix() + "-" + name();
+               String classPrefix = getClassPrefix();
+               return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
        }
 
        default String getClassPrefix() {
-               return "cms";
+               return "";
        }
 
 }
index 428c910e54133dee353aa8d034ffc388fb8f30a5..b90c017d9ac653f586f05149d325c56d42b53d80 100644 (file)
@@ -4,8 +4,11 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.StringTokenizer;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
@@ -17,6 +20,7 @@ import org.argeo.cms.ui.CmsConstants;
 import org.argeo.cms.ui.CmsView;
 import org.argeo.eclipse.ui.Selected;
 import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.argeo.jcr.JcrException;
 import org.argeo.jcr.JcrUtils;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.service.ResourceManager;
@@ -30,6 +34,7 @@ import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
@@ -113,6 +118,25 @@ public class CmsUiUtils implements CmsConstants {
                return NodeUtils.getDataPath(cn, node);
        }
 
+       /** Clean reserved URL characters for use in HTTP links. */
+       public static String getDataPathForUrl(Node node) throws RepositoryException {
+               return cleanPathForUrl(getDataPath(node));
+       }
+
+       /** Clean reserved URL characters for use in HTTP links. */
+       public static String cleanPathForUrl(String path) throws RepositoryException {
+               StringTokenizer st = new StringTokenizer(path, "/");
+               StringBuilder sb = new StringBuilder();
+               while (st.hasMoreElements()) {
+                       sb.append('/');
+                       String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
+                       encoded = encoded.replace("+", "%20");
+                       sb.append(encoded);
+
+               }
+               return sb.toString();
+       }
+
        /** @deprecated Use rowData16px() instead. GridData should not be reused. */
        @Deprecated
        public static RowData ROW_DATA_16px = new RowData(16, 16);
@@ -128,6 +152,7 @@ public class CmsUiUtils implements CmsConstants {
                return noSpaceGridLayout(new GridLayout(columns, false));
        }
 
+       /** @return the same layout, with spaces removed. */
        public static GridLayout noSpaceGridLayout(GridLayout layout) {
                layout.horizontalSpacing = 0;
                layout.verticalSpacing = 0;
@@ -159,6 +184,19 @@ public class CmsUiUtils implements CmsConstants {
        /*
         * ROW LAYOUT
         */
+       /** @return the same layout, with margins removed. */
+       public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
+               rowLayout.marginTop = 0;
+               rowLayout.marginBottom = 0;
+               rowLayout.marginLeft = 0;
+               rowLayout.marginRight = 0;
+               return rowLayout;
+       }
+
+       public static RowLayout noMarginsRowLayout(int type) {
+               return noMarginsRowLayout(new RowLayout(type));
+       }
+
        public static RowData rowData16px() {
                return new RowData(16, 16);
        }
@@ -186,7 +224,9 @@ public class CmsUiUtils implements CmsConstants {
                        return widget;// does nothing
                EclipseUiSpecificUtils.setStyleData(widget, style);
                if (widget instanceof Control) {
-                       CmsView.getCmsView((Control) widget).applyStyles(widget);
+                       CmsView cmsView = CmsView.getCmsView((Control) widget);
+                       if (cmsView != null)
+                               cmsView.applyStyles(widget);
                }
                return widget;
        }
@@ -202,6 +242,12 @@ public class CmsUiUtils implements CmsConstants {
                return widget;
        }
 
+       /** Disable markup validation. */
+       public static <T extends Widget> T disableMarkupValidation(T widget) {
+               EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
+               return widget;
+       }
+
        /**
         * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
         * 
@@ -245,6 +291,8 @@ public class CmsUiUtils implements CmsConstants {
 
        /** Dispose all children of a Composite */
        public static void clear(Composite composite) {
+               if (composite.isDisposed())
+                       return;
                for (Control child : composite.getChildren())
                        child.dispose();
        }
@@ -285,7 +333,13 @@ public class CmsUiUtils implements CmsConstants {
        }
 
        public static String img(String serverBase, Node fileNode, String width, String height) {
-               String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode);
+//             String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode);
+               String src;
+               try {
+                       src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get URL data path for " + fileNode, e);
+               }
                return imgBuilder(src, width, height).append("/>").toString();
        }
 
@@ -345,4 +399,5 @@ public class CmsUiUtils implements CmsConstants {
        /** Singleton. */
        private CmsUiUtils() {
        }
+
 }
index 698ab1b0692daf1a29225314465ccc8c153be119..6c4a870c49752f26731be354a492e49d7260d7da 100644 (file)
@@ -9,6 +9,8 @@ import static org.argeo.cms.ui.CmsConstants.NO_IMAGE_SIZE;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 
@@ -150,7 +152,7 @@ public class DefaultImageManager implements CmsImageManager {
        /** @return null if not available */
        @Override
        public String getImageUrl(Node node) throws RepositoryException {
-               return CmsUiUtils.getDataPath(node);
+               return CmsUiUtils.getDataPathForUrl(node);
        }
 
        protected String getResourceName(Node node) throws RepositoryException {
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java
new file mode 100644 (file)
index 0000000..4bbd19c
--- /dev/null
@@ -0,0 +1,6 @@
+package org.argeo.cms.ui.util;
+
+/** Simple styles used by the CMS UI utilities. */
+public enum SimpleStyle implements CmsStyle {
+       link;
+}
index 60db89978d9f1ba3493fc582edbce77bffdb1fe6..e5ae67a794c51a50842937c5ab42a53989a20e14 100644 (file)
@@ -164,7 +164,8 @@ public abstract class AbstractPageViewer extends ContentViewer implements Observ
                                        mouseListener = null;
                                refresh(getControl());
                                // layout(getControl());
-                               layoutPage();
+                               if (!getControl().isDisposed())
+                                       layoutPage();
                        } catch (RepositoryException e) {
                                throw new JcrException("Cannot refresh", e);
                        }
index 53c7dca0be0c0268734017d918addadab44fe3bf..9a0a269a24f6260798528659233524a7c323c9c7 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="ext/test"/>
index 498d09749982022d14d48fe879acfbc97a812227..ec161faa627ee16d8568032b94615dfba27a0729 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms">
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.filesServlet">
    <implementation class="org.argeo.cms.internal.http.CmsWebDavServlet"/>
    <service>
       <provide interface="javax.servlet.Servlet"/>
diff --git a/org.argeo.cms/OSGI-INF/pkgServlet.xml b/org.argeo.cms/OSGI-INF/pkgServlet.xml
new file mode 100644 (file)
index 0000000..30e5231
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
+   <implementation class="org.argeo.cms.internal.http.PkgServlet"/>
+   <service>
+      <provide interface="javax.servlet.Servlet"/>
+   </service>
+   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
+   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/pkgServletContext.xml b/org.argeo.cms/OSGI-INF/pkgServletContext.xml
new file mode 100644 (file)
index 0000000..7540a2c
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
+   <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
+   <service>
+      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+   </service>
+   <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
+   <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
+</scr:component>
index 9ecfb8f1aae88a3eaf7f44df2960ee6117eebe27..5af544206695c76471f8b2b2095479196d02da00 100644 (file)
@@ -7,12 +7,15 @@ org.apache.jackrabbit.webdav.server,\
 org.apache.jackrabbit.webdav.jcr,\
 org.apache.commons.httpclient.cookie;resolution:=optional,\
 !com.sun.security.jgss,\
+org.osgi.framework.namespace;version=0.0.0,\
 org.osgi.*;version=0.0.0,\
 org.osgi.service.http.whiteboard,\
 *
 
 Service-Component:\
 OSGI-INF/cmsUserManager.xml,\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml,\
 OSGI-INF/jcrServletContext.xml,\
 OSGI-INF/dataServletContext.xml,\
 OSGI-INF/filesServletContext.xml,\
index e9462c3add31cb7dbd0ef16f48afb00e2225a611..4c09650d4b0546bdc5c6220a23f23de99903cf95 100644 (file)
@@ -20,7 +20,6 @@ import org.argeo.api.security.NodeSecurityUtils;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.http.WebCmsSessionImpl;
-import org.argeo.cms.internal.kernel.Activator;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
@@ -28,7 +27,7 @@ import org.osgi.framework.ServiceReference;
 import org.osgi.service.http.HttpContext;
 import org.osgi.service.useradmin.Authorization;
 
-/** Centrlaises security related registrations. */
+/** Centralises security related registrations. */
 class CmsAuthUtils {
        // Standard
        final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME;
@@ -52,9 +51,7 @@ class CmsAuthUtils {
                // required for display name:
                subject.getPrivateCredentials().add(authorization);
 
-               if (Activator.isSingleUser()) {
-                       subject.getPrincipals().add(new DataAdminPrincipal());
-               }
+               boolean singleUser = authorization instanceof SingleUserAuthorization;
 
                Set<Principal> principals = subject.getPrincipals();
                try {
@@ -73,8 +70,9 @@ class CmsAuthUtils {
                                userPrincipal = new X500Principal(name.toString());
                                principals.add(userPrincipal);
 
-                               if (Activator.isSingleUser()) {
+                               if (singleUser) {
                                        principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal));
+                                       principals.add(new DataAdminPrincipal());
                                }
                        }
 
@@ -123,6 +121,7 @@ class CmsAuthUtils {
                // subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
        }
 
+       @SuppressWarnings("unused")
        synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject,
                        Authorization authorization, Locale locale) {
                // synchronized in order to avoid multiple registrations
@@ -131,49 +130,59 @@ class CmsAuthUtils {
                        HttpSession httpSession = request.getSession(false);
                        assert httpSession != null;
                        String httpSessId = httpSession.getId();
-                       String remoteUser = authorization.getName() != null ? authorization.getName()
-                                       : NodeConstants.ROLE_ANONYMOUS;
+                       boolean anonymous = authorization.getName() == null;
+                       String remoteUser = !anonymous ? authorization.getName() : NodeConstants.ROLE_ANONYMOUS;
                        request.setAttribute(HttpContext.REMOTE_USER, remoteUser);
                        request.setAttribute(HttpContext.AUTHORIZATION, authorization);
 
-                       CmsSessionImpl cmsSession = CmsSessionImpl.getByLocalId(httpSessId);
-                       if (cmsSession != null) {
-                               if (authorization.getName() != null) {
-                                       if (cmsSession.getAuthorization().getName() == null) {
-                                               cmsSession.close();
-                                               cmsSession = null;
-                                       } else if (!authorization.getName().equals(cmsSession.getAuthorization().getName())) {
+                       CmsSessionImpl cmsSession;
+                       CmsSessionImpl currentLocalSession = CmsSessionImpl.getByLocalId(httpSessId);
+                       if (currentLocalSession != null) {
+                               boolean currentLocalSessionAnonymous = currentLocalSession.getAuthorization().getName() == null;
+                               if (!anonymous) {
+                                       if (currentLocalSessionAnonymous) {
+                                               currentLocalSession.close();
+                                               // new CMS session
+                                               cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
+                                       } else if (!authorization.getName().equals(currentLocalSession.getAuthorization().getName())) {
                                                throw new IllegalStateException("Inconsistent user " + authorization.getName()
-                                                               + " for existing CMS session " + cmsSession);
-                                       }
-                                       // keyring
-                                       if (cmsSession != null)
+                                                               + " for existing CMS session " + currentLocalSession);
+                                       } else {
+                                               // keep current session
+                                               cmsSession = currentLocalSession;
+                                               // keyring
                                                subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys());
+                                       }
                                } else {// anonymous
-                                       if (cmsSession.getAuthorization().getName() != null) {
-                                               cmsSession.close();
-                                               // TODO rather throw an exception ? log a warning ?
-                                               cmsSession = null;
+                                       if (!currentLocalSessionAnonymous) {
+                                               currentLocalSession.close();
+                                               throw new IllegalStateException(
+                                                               "Existing CMS session " + currentLocalSession + " was not logged out properly.");
                                        }
+                                       // keep current session
+                                       cmsSession = currentLocalSession;
                                }
-                       } else if (cmsSession == null) {
+                       } else {
+                               // new CMS session
                                cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
                        }
-                       // request.setAttribute(CmsSession.class.getName(), cmsSession);
-                       if (cmsSession != null) {
-                               CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
-                               if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0)
-                                       subject.getPrivateCredentials().add(nodeSessionId);
-                               else {
-                                       UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next()
-                                                       .getUuid();
-                                       // if (storedSessionId.equals(httpSessionId.getValue()))
-                                       throw new IllegalStateException(
-                                                       "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
-                               }
+
+                       if (cmsSession == null)// should be dead code (cf. SuppressWarning of the method)
+                               throw new IllegalStateException("CMS session cannot be null");
+
+                       CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
+                       if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0) {
+                               subject.getPrivateCredentials().add(nodeSessionId);
+                       } else {
+                               UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
+                               // if (storedSessionId.equals(httpSessionId.getValue()))
+                               throw new IllegalStateException(
+                                               "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
                        }
                } else {
-                       // TODO desktop, CLI
+                       CmsSessionImpl cmsSession = new CmsSessionImpl(subject, authorization, locale, "desktop");
+                       CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
+                       subject.getPrivateCredentials().add(nodeSessionId);
                }
        }
 
index b4144d30f85761c701a96dd072f3365e7e3a82c0..af4e636d88897ee6a2e9921b595ce0077ff5d91c 100644 (file)
@@ -8,10 +8,15 @@ import org.osgi.service.useradmin.Authorization;
  * @see SingleUserLoginModule
  */
 public class SingleUserAuthorization implements Authorization {
+       private String name;
+
+       public SingleUserAuthorization(String name) {
+               this.name = name;
+       }
 
        @Override
        public String getName() {
-               return System.getProperty("user.name");
+               return name;
        }
 
        @Override
index 0b163bac3fb9c12c98f5f85c8a2d36dde0e9abb2..240564f9ec894b809c956829e01fc653dc8d0942 100644 (file)
@@ -2,10 +2,8 @@ package org.argeo.cms.auth;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.security.Principal;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 
 import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
@@ -18,11 +16,9 @@ import javax.servlet.http.HttpServletRequest;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.security.DataAdminPrincipal;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.naming.LdapAttrs;
 import org.argeo.osgi.useradmin.IpaUtils;
+import org.argeo.osgi.useradmin.OsUserUtils;
 import org.osgi.service.useradmin.Authorization;
 
 /** Login module for when the system is owned by a single user. */
@@ -50,11 +46,12 @@ public class SingleUserLoginModule implements LoginModule {
 
        @Override
        public boolean commit() throws LoginException {
-               X500Principal principal;
+               String authorizationName;
                KerberosPrincipal kerberosPrincipal = CmsAuthUtils.getSinglePrincipal(subject, KerberosPrincipal.class);
                if (kerberosPrincipal != null) {
                        LdapName userDn = IpaUtils.kerberosToDn(kerberosPrincipal.getName());
-                       principal = new X500Principal(userDn.toString());
+                       X500Principal principal = new X500Principal(userDn.toString());
+                       authorizationName = principal.getName();
                } else {
                        Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        if (username == null)
@@ -67,12 +64,9 @@ public class SingleUserLoginModule implements LoginModule {
                                hostname = "localhost";
                        }
                        String baseDn = ("." + hostname).replaceAll("\\.", ",dc=");
-                       principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn);
+                       X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn);
+                       authorizationName = principal.getName();
                }
-               Set<Principal> principals = subject.getPrincipals();
-               principals.add(principal);
-               principals.add(new ImpliedByPrincipal(NodeConstants.ROLE_ADMIN, principal));
-               principals.add(new DataAdminPrincipal());
 
                HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
                Locale locale = Locale.getDefault();
@@ -80,8 +74,18 @@ public class SingleUserLoginModule implements LoginModule {
                        locale = request.getLocale();
                if (locale == null)
                        locale = Locale.getDefault();
-               Authorization authorization = new SingleUserAuthorization();
+               Authorization authorization = new SingleUserAuthorization(authorizationName);
                CmsAuthUtils.addAuthorization(subject, authorization);
+               
+               // Add standard Java OS login 
+               OsUserUtils.loginAsSystemUser(subject);
+
+               // additional principals (must be after Authorization registration)
+//             Set<Principal> principals = subject.getPrincipals();
+//             principals.add(principal);
+//             principals.add(new ImpliedByPrincipal(NodeConstants.ROLE_ADMIN, principal));
+//             principals.add(new DataAdminPrincipal());
+
                CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
 
                return true;
index 4b72903e22ae3dc66933ca0e91e267ff7b13b581..092a06b7778e8bd6ecbcff7b53293ce3494a36b2 100644 (file)
@@ -33,7 +33,6 @@ import org.argeo.cms.internal.kernel.Activator;
 import org.argeo.naming.LdapAttrs;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.argeo.osgi.useradmin.IpaUtils;
-import org.argeo.osgi.useradmin.OsUserUtils;
 import org.argeo.osgi.useradmin.TokenUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
@@ -64,7 +63,7 @@ public class UserAdminLoginModule implements LoginModule {
 
        private Authorization bindAuthorization = null;
 
-       private boolean singleUser = Activator.isSingleUser();
+//     private boolean singleUser = Activator.isSingleUser();
 
        @SuppressWarnings("unchecked")
        @Override
@@ -113,11 +112,11 @@ public class UserAdminLoginModule implements LoginModule {
                        username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        password = null;
                        preauth = true;
-               } else if (singleUser) {
-                       username = OsUserUtils.getOsUsername();
-                       password = null;
-                       // TODO retrieve from http session
-                       locale = Locale.getDefault();
+//             } else if (singleUser) {
+//                     username = OsUserUtils.getOsUsername();
+//                     password = null;
+//                     // TODO retrieve from http session
+//                     locale = Locale.getDefault();
                } else {
 
                        // ask for username and password
@@ -194,8 +193,8 @@ public class UserAdminLoginModule implements LoginModule {
                        // TODO check CRLs/OSCP validity?
                        // NB: authorization in commit() will work only if an LDAP connection password
                        // is provided
-               } else if (singleUser) {
-                       // TODO verify IP address?
+//             } else if (singleUser) {
+//                     // TODO verify IP address?
                } else if (preauth) {
                        // ident
                } else {
@@ -211,9 +210,9 @@ public class UserAdminLoginModule implements LoginModule {
                if (locale != null)
                        subject.getPublicCredentials().add(locale);
 
-               if (singleUser) {
-                       OsUserUtils.loginAsSystemUser(subject);
-               }
+//             if (singleUser) {
+//                     OsUserUtils.loginAsSystemUser(subject);
+//             }
                UserAdmin userAdmin = Activator.getUserAdmin();
                Authorization authorization;
                if (callbackHandler == null) {// anonymous
@@ -308,6 +307,7 @@ public class UserAdminLoginModule implements LoginModule {
                        Set<User> collectedUsers = new HashSet<>();
                        // try dn
                        User user = null;
+                       user = null;
                        // try all indexes
                        for (String attr : indexedUserProperties) {
                                user = userAdmin.getUser(attr, providedUsername);
index b6966765d9534ea1469188dc5a81c06b1cf80fa3..f40c6fffd561d6315239c96109863fa2d9c35495 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.cms.internal.auth;
 
+import java.io.Serializable;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -10,7 +11,6 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
-import java.util.LinkedHashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -40,15 +40,16 @@ import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.useradmin.Authorization;
 
 /** Default CMS session implementation. */
-public class CmsSessionImpl implements CmsSession {
+public class CmsSessionImpl implements CmsSession, Serializable {
+       private static final long serialVersionUID = 1867719354246307225L;
        private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext();
        private final static Log log = LogFactory.getLog(CmsSessionImpl.class);
 
        // private final Subject initialSubject;
-       private final AccessControlContext initialContext;
+       private transient AccessControlContext accessControlContext;
        private final UUID uuid;
        private final String localSessionId;
-       private final Authorization authorization;
+       private Authorization authorization;
        private final LdapName userDn;
        private final boolean anonymous;
 
@@ -60,14 +61,14 @@ public class CmsSessionImpl implements CmsSession {
 
        private Map<String, Session> dataSessions = new HashMap<>();
        private Set<String> dataSessionsInUse = new HashSet<>();
-       private LinkedHashSet<Session> additionalDataSessions = new LinkedHashSet<>();
+       private Set<Session> additionalDataSessions = new HashSet<>();
 
        private Map<String, Object> views = new HashMap<>();
 
        public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) {
                this.creationTime = ZonedDateTime.now();
                this.locale = locale;
-               this.initialContext = Subject.doAs(initialSubject, new PrivilegedAction<AccessControlContext>() {
+               this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction<AccessControlContext>() {
 
                        @Override
                        public AccessControlContext run() {
@@ -120,23 +121,28 @@ public class CmsSessionImpl implements CmsSession {
                        lc.logout();
                } catch (LoginException e) {
                        log.warn("Could not logout " + getSubject() + ": " + e);
+               } finally {
+                       accessControlContext = null;
                }
                log.debug("Closed " + this);
        }
 
        private Subject getSubject() {
-               return Subject.getSubject(initialContext);
+               return Subject.getSubject(accessControlContext);
        }
 
        public Set<SecretKey> getSecretKeys() {
+               checkValid();
                return getSubject().getPrivateCredentials(SecretKey.class);
        }
 
        public Session newDataSession(String cn, String workspace, Repository repository) {
+               checkValid();
                return login(repository, workspace);
        }
 
        public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
+               checkValid();
                // FIXME make it more robust
                if (workspace == null)
                        workspace = NodeConstants.SYS_WORKSPACE;
@@ -207,12 +213,18 @@ public class CmsSessionImpl implements CmsSession {
                return !isClosed();
        }
 
-       protected boolean isClosed() {
+       private void checkValid() {
+               if (!isValid())
+                       throw new IllegalStateException("CMS session " + uuid + " is not valid since " + end);
+       }
+
+       final protected boolean isClosed() {
                return getEnd() != null;
        }
 
        @Override
        public Authorization getAuthorization() {
+               checkValid();
                return authorization;
        }
 
@@ -258,6 +270,7 @@ public class CmsSessionImpl implements CmsSession {
 
        @Override
        public void registerView(String uid, Object view) {
+               checkValid();
                if (views.containsKey(uid))
                        throw new IllegalArgumentException("View " + uid + " is already registered.");
                views.put(uid, view);
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java
new file mode 100644 (file)
index 0000000..9cbd21c
--- /dev/null
@@ -0,0 +1,134 @@
+package org.argeo.cms.internal.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.PublishNamespace;
+import org.argeo.osgi.util.FilterRequirement;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.resource.Requirement;
+
+public class PkgServlet extends HttpServlet {
+       private static final long serialVersionUID = 7660824185145214324L;
+
+       private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
+
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               String pathInfo = req.getPathInfo();
+
+               String pkg, versionStr, file;
+               String[] parts = pathInfo.split("/");
+               // first is always empty
+               if (parts.length == 4) {
+                       pkg = parts[1];
+                       versionStr = parts[2];
+                       file = parts[3];
+               } else if (parts.length == 3) {
+                       pkg = parts[1];
+                       versionStr = null;
+                       file = parts[2];
+               } else {
+                       throw new IllegalArgumentException("Unsupported path length " + pathInfo);
+               }
+
+               FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
+               String filter;
+               if (versionStr == null) {
+                       filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
+               } else {
+                       if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
+                               VersionRange versionRange = new VersionRange(versionStr);
+                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
+                                               + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
+
+                       } else {
+                               Version version = new Version(versionStr);
+                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
+                                               + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
+                       }
+               }
+               Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
+               Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
+               if (packages.isEmpty()) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               // TODO verify that it works with multiple versions
+               SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
+               for (BundleCapability capability : packages) {
+                       sorted.put(capability.getRevision().getVersion(), capability);
+               }
+
+               Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
+               String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
+               URL internalURL = bundle.getResource(entryPath);
+               if (internalURL == null) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               // Resource found, we now check whether it can be published
+               boolean publish = false;
+               BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
+               capabilities: for (BundleCapability bundleCapability : bundleWiring
+                               .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
+                       Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
+                       if (publishedPkg != null) {
+                               if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
+                                       Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
+                                       if (publishedFile == null) {
+                                               publish = true;
+                                               break capabilities;
+                                       } else {
+                                               String[] publishedFiles = publishedFile.toString().split(",");
+                                               for (String pattern : publishedFiles) {
+                                                       if (pattern.startsWith("*.")) {
+                                                               String ext = pattern.substring(1);
+                                                               if (file.endsWith(ext)) {
+                                                                       publish = true;
+                                                                       break capabilities;
+                                                               }
+                                                       } else {
+                                                               if (publishedFile.equals(file)) {
+                                                                       publish = true;
+                                                                       break capabilities;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               if (!publish) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               try (InputStream in = internalURL.openStream()) {
+                       IOUtils.copy(in, resp.getOutputStream());
+               }
+       }
+
+}
index 2e703ced844da9138735a6b08034217d7ba3ca7e..c2898577eeccbac0ce21345d8c98a64af35b4ea9 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.cms.internal.jcr;
 
 import java.util.Properties;
 
+import org.apache.jackrabbit.core.config.BeanConfig;
 import org.apache.jackrabbit.core.config.ConfigurationException;
 import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
 import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig;
@@ -14,7 +15,7 @@ import org.w3c.dom.Element;
  */
 @SuppressWarnings("restriction")
 class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser {
-       private ClassLoader accessControlProviderClassLoader = null;
+       private ClassLoader classLoader = null;
 
        public CustomRepositoryConfigurationParser(Properties variables) {
                super(variables);
@@ -30,19 +31,28 @@ class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser
                props.putAll(variables);
                CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props,
                                connectionFactory);
-               subParser.setAccessControlProviderClassLoader(accessControlProviderClassLoader);
+               subParser.setClassLoader(classLoader);
                return subParser;
        }
 
        @Override
        public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException {
                WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent);
-               workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(accessControlProviderClassLoader);
+               workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader);
                return workspaceSecurityConfig;
        }
 
-       public void setAccessControlProviderClassLoader(ClassLoader accessControlProviderClassLoader) {
-               this.accessControlProviderClassLoader = accessControlProviderClassLoader;
+       @Override
+       protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException {
+               BeanConfig beanConfig = super.parseBeanConfig(parent, name);
+               if (beanConfig.getClassName().startsWith("org.argeo")) {
+                       beanConfig.setClassLoader(classLoader);
+               }
+               return beanConfig;
+       }
+
+       public void setClassLoader(ClassLoader classLoader) {
+               this.classLoader = classLoader;
        }
 
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java
new file mode 100644 (file)
index 0000000..dba005c
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.cms.internal.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataRecord;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.FileDataStore;
+
+/**
+ * <b>experimental</b> Duplicate added entries in another directory (typically a
+ * remote mount).
+ */
+@SuppressWarnings("restriction")
+public class LocalFsDataStore extends FileDataStore {
+       String redundantPath;
+       FileDataStore redundantStore;
+
+       @Override
+       public void init(String homeDir) {
+               // init primary first
+               super.init(homeDir);
+
+               if (redundantPath != null) {
+                       // redundant directory must be created first
+                       // TODO implement some polling?
+                       if (Files.exists(Paths.get(redundantPath))) {
+                               redundantStore = new FileDataStore();
+                               redundantStore.setPath(redundantPath);
+                               redundantStore.init(homeDir);
+                       }
+               }
+       }
+
+       @Override
+       public DataRecord addRecord(InputStream input) throws DataStoreException {
+               DataRecord dataRecord = super.addRecord(input);
+               syncRedundantRecord(dataRecord);
+               return dataRecord;
+       }
+
+       @Override
+       public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
+               DataRecord dataRecord = super.getRecord(identifier);
+               syncRedundantRecord(dataRecord);
+               return dataRecord;
+       }
+
+       protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException {
+               if (redundantStore == null)
+                       return;
+               if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) {
+                       try (InputStream redundant = dataRecord.getStream()) {
+                               redundantStore.addRecord(redundant);
+                       } catch (IOException e) {
+                               throw new DataStoreException("Cannot add redundant record.", e);
+                       }
+               }
+       }
+
+       public void setRedundantPath(String redundantPath) {
+               this.redundantPath = redundantPath;
+       }
+
+}
index 9480a2913d663ce40ba3d43e15859c02a8146bb3..fbb1e4f7a03f1a380f549b07da5fc952498ba84a 100644 (file)
@@ -61,7 +61,7 @@ public class RepositoryBuilder {
 
                        // custom configuration parser
                        CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars);
-                       parser.setAccessControlProviderClassLoader(cl);
+                       parser.setClassLoader(cl);
                        RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config);
                        repositoryConfig.init();
 
index ff181f19599f633a39b64985fe67a7debee90d52..b430674c9acd5a6825072509d1aba36a967dbc19 100644 (file)
        </DataSources>
 
        <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+       <FileSystem
+               class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
                <param name="dataSourceName" value="dataSource" />
                <param name="schema" value="postgresql" />
                <param name="schemaObjectPrefix" value="fs_" />
        </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+       <DataStore
+               class="org.argeo.cms.internal.jcr.LocalFsDataStore">
                <param name="path" value="${rep.home}/../datastore" />
+               <param name="redundantPath" value="${rep.home}/../datastorer" />
        </DataStore>
 
        <!-- Workspace templates -->
        <Workspaces rootPath="${rep.home}/workspaces"
                defaultWorkspace="${defaultWorkspace}" />
        <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+               <FileSystem
+                       class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
                        <param name="dataSourceName" value="dataSource" />
                        <param name="schema" value="postgresql" />
                        <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
                        <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
                        <param name="bundleCacheSize" value="${bundleCacheMB}" />
                </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+               <SearchIndex
+                       class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path"
+                               value="${indexesBase}/${cn}/${wsp.name}/index" />
                        <param name="extractorPoolSize" value="${extractorPoolSize}" />
                        <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+                       <param name="maxVolatileIndexSize"
+                               value="${maxVolatileIndexSize}" />
                </SearchIndex>
                <WorkspaceSecurity>
                        <AccessControlProvider
@@ -52,7 +59,8 @@
 
        <!-- Versioning -->
        <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+               <FileSystem
+                       class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
                        <param name="dataSourceName" value="dataSource" />
                        <param name="schema" value="postgresql" />
                        <param name="schemaObjectPrefix" value="fs_ver_" />
        </Versioning>
 
        <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+       <SearchIndex
+               class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
                <param name="path" value="${indexesBase}/${cn}/index" />
                <param name="extractorPoolSize" value="${extractorPoolSize}" />
                <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+               <param name="maxVolatileIndexSize"
+                       value="${maxVolatileIndexSize}" />
        </SearchIndex>
 
        <!-- Security -->
        <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+               <SecurityManager
+                       class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
                        workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+               <AccessManager
+                       class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
        </Security>
 
        <!-- Clustering -->
-       <Cluster id="${clusterId}">
-               <Journal class="org.apache.jackrabbit.core.journal.DatabaseJournal">
+       <Cluster id="${clusterId}" syncDelay="100">
+               <Journal
+                       class="org.apache.jackrabbit.core.journal.DatabaseJournal">
                        <param name="dataSourceName" value="dataSource" />
                        <param name="schemaObjectPrefix" value="journal_" />
                </Journal>
index 8086c8636440f9700ecb8e58f894d1a13731730c..6d50f3dabed2f2c1de6b758c6fb35080a37a7f4c 100644 (file)
@@ -226,6 +226,7 @@ public class Activator implements BundleActivator {
                return getNodeUserAdmin().getAcceptorCredentials();
        }
 
+       @Deprecated
        public static boolean isSingleUser() {
                return getNodeUserAdmin().isSingleUser();
        }
index 40e23541ba0ad46b3c444acfd586a094c3a4d9d0..1a9817450ed427deee762ed39f0866d1d6a3942c 100644 (file)
@@ -96,20 +96,25 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor
        @Override
        public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
                String uri = (String) properties.get(UserAdminConf.uri.name());
+               Object realm = properties.get(UserAdminConf.realm.name());
                URI u;
                try {
                        if (uri == null) {
                                String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
                                u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
-                       } else
+                       } else if (realm != null) {
+                               u = null;
+                       } else {
                                u = new URI(uri);
+                       }
                } catch (URISyntaxException e) {
                        throw new IllegalArgumentException("Badly formatted URI " + uri, e);
                }
 
                // Create
                AbstractUserDirectory userDirectory;
-               if (UserAdminConf.SCHEME_LDAP.equals(u.getScheme())) {
+               if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
+                               || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
                        userDirectory = new LdapUserAdmin(properties);
                } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
                        userDirectory = new LdifUserAdmin(u, properties);
@@ -119,7 +124,6 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor
                } else {
                        throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
                }
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
                addUserDirectory(userDirectory);
 
                // OSGi
@@ -135,9 +139,10 @@ class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactor
                pidToBaseDn.put(pid, baseDn);
                // pidToServiceRegs.put(pid, reg);
 
-               if (log.isDebugEnabled())
-                       log.debug("User directory " + userDirectory.getBaseDn() + " [" + u.getScheme() + "] enabled."
-                                       + (realm != null ? " " + realm + " realm." : ""));
+               if (log.isDebugEnabled()) {
+                       log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
+                                       + " enabled." + (realm != null ? " " + realm + " realm." : ""));
+               }
 
                if (isSystemRolesBaseDn(baseDn)) {
                        // publishes only when system roles are available
index 9ff8f855f662db1c14bd8e2f33bfce8417123f3a..c88ee7f93c1a7b86994c504360dd6c915121147c 100644 (file)
@@ -21,7 +21,7 @@ import org.osgi.framework.FrameworkUtil;
 import org.osgi.service.http.context.ServletContextHelper;
 
 /**
- * Default servlet context degrading to anonymous if the the sesison is not
+ * Default servlet context degrading to anonymous if the the session is not
  * pre-authenticated.
  */
 public class CmsServletContext extends ServletContextHelper {
index 53c7dca0be0c0268734017d918addadab44fe3bf..9a0a269a24f6260798528659233524a7c323c9c7 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="ext/test"/>
index 457b115718901142f2212cc654481644186b3114..e03d341b1bf12f700f7f300a440a400ad86bd35c 100644 (file)
@@ -4,6 +4,6 @@
        <classpathentry kind="con"
                path="org.eclipse.pde.core.requiredPlugins" />
        <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8" />
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
        <classpathentry kind="output" path="bin" />
 </classpath>
index f79c7fd88ab910ca16702d3e1ccdcdf4226e98d0..a89b921cdf6a9012bd38107ed647f046d07644f2 100644 (file)
@@ -22,6 +22,10 @@ public class EclipseUiSpecificUtils {
                widget.setData(RWT.MARKUP_ENABLED, true);
        }
 
+       public static void setMarkupValidationDisabledData(Widget widget) {
+               widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
+       }
+
        /**
         * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
         */
index 457b115718901142f2212cc654481644186b3114..e03d341b1bf12f700f7f300a440a400ad86bd35c 100644 (file)
@@ -4,6 +4,6 @@
        <classpathentry kind="con"
                path="org.eclipse.pde.core.requiredPlugins" />
        <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8" />
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
        <classpathentry kind="output" path="bin" />
 </classpath>
index 4e5da1da654885181e6bd7f78268ac345aa09d44..b21df7df88879493c7a882566b17db7a2ae0dfae 100644 (file)
@@ -5,6 +5,6 @@
        <classpathentry kind="con"
                path="org.eclipse.pde.core.requiredPlugins" />
        <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8" />
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
        <classpathentry kind="output" path="bin" />
 </classpath>
index 62667c5180920001f1bb9c679487886a0b97d280..d9358c0830ee71f6130649e611a95f77eb3defae 100644 (file)
@@ -79,7 +79,7 @@ public class DnsBrowser implements Closeable {
        }
 
        /** Ordered, with preferred first. */
-       public List<String> getSrvRecordsAsHosts(String name) throws NamingException {
+       public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
                List<String> raw = getRecords(name, "SRV");
                if (raw.size() == 0)
                        return null;
@@ -96,7 +96,7 @@ public class DnsBrowser implements Closeable {
                }
                List<String> lst = new ArrayList<>();
                for (SrvRecord order : res) {
-                       lst.add(order.toHost());
+                       lst.add(order.toHost(withPort));
                }
                return Collections.unmodifiableList(lst);
        }
index d3515889a2988cc8d8dc5d6dba076e3360b3379f..8ecc9445790a1bcc32394e46f40484c18552b5f1 100644 (file)
@@ -20,10 +20,10 @@ class SrvRecord implements Comparable<SrvRecord> {
                        return priority - other.priority;
                if (weight != other.weight)
                        return other.weight - other.weight;
-               String host = toHost();
-               String otherHost = other.toHost();
+               String host = toHost(false);
+               String otherHost = other.toHost(false);
                if (host.length() == otherHost.length())
-                       return toHost().compareTo(other.toHost());
+                       return host.compareTo(otherHost);
                else
                        return host.length() - otherHost.length();
        }
@@ -43,10 +43,10 @@ class SrvRecord implements Comparable<SrvRecord> {
                return priority + " " + weight;
        }
 
-       public String toHost() {
+       public String toHost(boolean withPort) {
                String hostStr = hostname;
                if (hostname.charAt(hostname.length() - 1) == '.')
                        hostStr = hostname.substring(0, hostname.length() - 1);
-               return hostStr + ":" + port;
+               return hostStr + (withPort ? ":" + port : "");
        }
 }
index 610f3f6400ca0bcc5d8753f9242192a09cd95ccb..f2d7c88fc232ca8d1090c065a5a2a5f9c1b5a975 100644 (file)
@@ -51,13 +51,15 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
 
        private final boolean readOnly;
        private final boolean disabled;
-       private final URI uri;
+       private final String uri;
 
        private UserAdmin externalRoles;
        // private List<String> indexedUserProperties = Arrays
        // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
        // LdapAttrs.cn.name() });
 
+       private final boolean scoped;
+
        private String memberAttributeId = "member";
        private List<String> credentialAttributeIds = Arrays
                        .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
@@ -66,7 +68,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
        private TransactionManager transactionManager;
        private WcXaResource xaResource = new WcXaResource(this);
 
-       public AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props) {
+       AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+               this.scoped = scoped;
                properties = new Hashtable<String, Object>();
                for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
                        String key = keys.nextElement();
@@ -74,18 +77,14 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
                }
 
                if (uriArg != null) {
-                       uri = uriArg;
+                       uri = uriArg.toString();
                        // uri from properties is ignored
                } else {
                        String uriStr = UserAdminConf.uri.getValue(properties);
                        if (uriStr == null)
                                uri = null;
                        else
-                               try {
-                                       uri = new URI(uriStr);
-                               } catch (URISyntaxException e) {
-                                       throw new UserDirectoryException("Badly formatted URI " + uriStr, e);
-                               }
+                               uri = uriStr;
                }
 
                userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
@@ -175,14 +174,16 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
                Attributes attrs = user.getAttributes();
                // TODO centralize attribute name
                Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
-               if (memberOf != null) {
+               // if user belongs to this directory, we only check meberOf
+               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
                        try {
                                NamingEnumeration<?> values = memberOf.getAll();
                                while (values.hasMore()) {
                                        Object value = values.next();
                                        LdapName groupDn = new LdapName(value.toString());
                                        DirectoryUser group = doGetRole(groupDn);
-                                       allRoles.add(group);
+                                       if (group != null)
+                                               allRoles.add(group);
                                }
                        } catch (Exception e) {
                                throw new UserDirectoryException("Cannot get memberOf groups for " + user, e);
@@ -191,8 +192,10 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
                        for (LdapName groupDn : getDirectGroups(user.getDn())) {
                                // TODO check for loops
                                DirectoryUser group = doGetRole(groupDn);
-                               allRoles.add(group);
-                               collectRoles(group, allRoles);
+                               if (group != null) {
+                                       allRoles.add(group);
+                                       collectRoles(group, allRoles);
+                               }
                        }
                }
        }
@@ -398,13 +401,20 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
                return credentialAttributeIds;
        }
 
-       protected URI getUri() {
+       protected String getUri() {
                return uri;
        }
 
-       private static boolean readOnlyDefault(URI uri) {
-               if (uri == null)
+       private static boolean readOnlyDefault(String uriStr) {
+               if (uriStr == null)
                        return true;
+               /// TODO make it more generic
+               URI uri;
+               try {
+                       uri = new URI(uriStr.split(" ")[0]);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException(e);
+               }
                if (uri.getScheme() == null)
                        return false;// assume relative file to be writable
                if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
@@ -488,4 +498,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
                return xaResource;
        }
 
+       public boolean isScoped() {
+               return scoped;
+       }
+
 }
index 66d46d4e94c2acfe3cdb1807bea2ac0b00e018fd..bee513546cdd25a0f0adad343d07b2857ca8cab7 100644 (file)
@@ -74,9 +74,9 @@ public class AggregatingUserAdmin implements UserAdmin {
        public User getUser(String key, String value) {
                List<User> res = new ArrayList<User>();
                for (UserAdmin userAdmin : businessRoles.values()) {
-                       User u = userAdmin.getUser(key, value);
-                       if (u != null)
-                               res.add(u);
+                               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;
@@ -87,11 +87,12 @@ public class AggregatingUserAdmin implements UserAdmin {
                if (user == null) {// anonymous
                        return systemRoles.getAuthorization(null);
                }
-               UserAdmin userAdmin = findUserAdmin(user.getName());
-               Authorization rawAuthorization = userAdmin.getAuthorization(user);
+               AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName());
+               Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
                String usernameToUse;
                String displayNameToUse;
                if (user instanceof Group) {
+                       // TODO check whether this is still working
                        String ownerDn = TokenUtils.userDn((Group) user);
                        if (ownerDn != null) {// tokens
                                UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
@@ -106,21 +107,38 @@ public class AggregatingUserAdmin implements UserAdmin {
                        usernameToUse = rawAuthorization.getName();
                        displayNameToUse = rawAuthorization.toString();
                }
-               // gather system roles
-               Set<String> sysRoles = new HashSet<String>();
-               for (String role : rawAuthorization.getRoles()) {
-                       Authorization auth = systemRoles.getAuthorization((User) userAdmin.getRole(role));
-                       systemRoles: for (String systemRole : auth.getRoles()) {
-                               if (role.equals(systemRole))
-                                       continue systemRoles;
-                               sysRoles.add(systemRole);
-                       }
+
+               // gather roles from other referentials
+               final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating
+               if (user instanceof DirectoryUser) {
+                       userAdminToUse = userReferentialOfThisUser;
+               } else if (user instanceof AuthenticatingUser) {
+                       userAdminToUse = userReferentialOfThisUser.scope(user);
+               } else {
+                       throw new IllegalArgumentException("Unsupported user type " + user.getClass());
+               }
+
+               try {
+                       Set<String> sysRoles = new HashSet<String>();
+                       for (String role : rawAuthorization.getRoles()) {
+                               User userOrGroup = (User) userAdminToUse.getRole(role);
+                               Authorization auth = systemRoles.getAuthorization(userOrGroup);
+                               systemRoles: for (String systemRole : auth.getRoles()) {
+                                       if (role.equals(systemRole))
+                                               continue systemRoles;
+                                       sysRoles.add(systemRole);
+                               }
 //                     sysRoles.addAll(Arrays.asList(auth.getRoles()));
+                       }
+                       addAbstractSystemRoles(rawAuthorization, sysRoles);
+                       Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
+                                       rawAuthorization.getRoles());
+                       return authorization;
+               } finally {
+                       if (userAdminToUse != null && userAdminToUse.isScoped()) {
+                               userAdminToUse.destroy();
+                       }
                }
-               addAbstractSystemRoles(rawAuthorization, sysRoles);
-               Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
-                               rawAuthorization.getRoles());
-               return authorization;
        }
 
        /**
@@ -155,16 +173,26 @@ public class AggregatingUserAdmin implements UserAdmin {
        protected void postAdd(AbstractUserDirectory userDirectory) {
        }
 
-       private UserAdmin findUserAdmin(String name) {
+//     private UserAdmin findUserAdmin(User user) {
+//             if (user == null)
+//                     throw new IllegalArgumentException("User should not be null");
+//             AbstractUserDirectory userAdmin = findUserAdmin(user.getName());
+//             if (user instanceof DirectoryUser) {
+//                     return userAdmin;
+//             } else {
+//                     return userAdmin.scope(user);
+//             }
+//     }
+
+       private AbstractUserDirectory findUserAdmin(String name) {
                try {
-                       UserAdmin userAdmin = findUserAdmin(new LdapName(name));
-                       return userAdmin;
+                       return findUserAdmin(new LdapName(name));
                } catch (InvalidNameException e) {
                        throw new UserDirectoryException("Badly formatted name " + name, e);
                }
        }
 
-       private UserAdmin findUserAdmin(LdapName name) {
+       private AbstractUserDirectory findUserAdmin(LdapName name) {
                if (name.startsWith(systemRolesBaseDn))
                        return systemRoles;
                if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
index 9d0056c55bddfc1c2d82cce29c690bc4200265fb..d56c06ac0964b8295fcfc655c4dc15ba0cf478a6 100644 (file)
@@ -1,8 +1,19 @@
 package org.argeo.osgi.useradmin;
 
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
 import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.naming.DnsBrowser;
 import org.argeo.naming.LdapAttrs;
 
 /** Free IPA specific conventions. */
@@ -16,10 +27,19 @@ public class IpaUtils {
        public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
                        + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
 
+       @Deprecated
        static String domainToUserDirectoryConfigPath(String realm) {
                return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm;
        }
 
+       public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
+               properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm));
+               properties.put(UserAdminConf.realm.name(), realm);
+               properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE);
+               properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE);
+               properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString());
+       }
+
        public static String domainToBaseDn(String domain) {
                String[] dcs = domain.split("\\.");
                StringBuilder sb = new StringBuilder();
@@ -51,4 +71,67 @@ public class IpaUtils {
        private IpaUtils() {
 
        }
+
+       public static String kerberosDomainFromDns() {
+               String kerberosDomain;
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       InetAddress localhost = InetAddress.getLocalHost();
+                       String hostname = localhost.getHostName();
+                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+                       kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+                       return kerberosDomain;
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
+               }
+
+       }
+
+       public static Dictionary<String, Object> convertIpaUri(URI uri) {
+               String path = uri.getPath();
+               String kerberosRealm;
+               if (path == null || path.length() <= 1) {
+                       kerberosRealm = kerberosDomainFromDns();
+               } else {
+                       kerberosRealm = path.substring(1);
+               }
+
+               if (kerberosRealm == null)
+                       throw new UserDirectoryException("No Kerberos domain available for " + uri);
+               // TODO intergrate CA certificate in truststore
+               // String schemeToUse = SCHEME_LDAPS;
+               String schemeToUse = UserAdminConf.SCHEME_LDAP;
+               List<String> ldapHosts;
+               String ldapHostsStr = uri.getHost();
+               if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
+                       try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                               ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
+                                               schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false);
+                               if (ldapHosts == null || ldapHosts.size() == 0) {
+                                       throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
+                               } else {
+                                       ldapHostsStr = ldapHosts.get(0);
+                               }
+                       } catch (NamingException | IOException e) {
+                               throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
+                       }
+               } else {
+                       ldapHosts = new ArrayList<>();
+                       ldapHosts.add(ldapHostsStr);
+               }
+
+               StringBuilder uriStr = new StringBuilder();
+               try {
+                       for (String host : ldapHosts) {
+                               URI convertedUri = new URI(schemeToUse + "://" + host + "/");
+                               uriStr.append(convertedUri).append(' ');
+                       }
+               } catch (URISyntaxException e) {
+                       throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
+               }
+
+               Hashtable<String, Object> res = new Hashtable<>();
+               res.put(UserAdminConf.uri.name(), uriStr.toString());
+               addIpaConfig(kerberosRealm, res);
+               return res;
+       }
 }
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java
new file mode 100644 (file)
index 0000000..3e869f3
--- /dev/null
@@ -0,0 +1,147 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.CommunicationException;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.naming.LdapAttrs;
+
+/** A synchronized wrapper for a single {@link InitialLdapContext}. */
+// TODO implement multiple contexts and connection pooling.
+class LdapConnection {
+       private InitialLdapContext initialLdapContext = null;
+
+       LdapConnection(String url, Dictionary<String, ?> properties) {
+               try {
+                       Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
+                       connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+                       connEnv.put(Context.PROVIDER_URL, url);
+                       connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
+                       // use pooling in order to avoid connection timeout
+//                     connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
+//                     connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
+
+                       initialLdapContext = new InitialLdapContext(connEnv, null);
+                       // StartTlsResponse tls = (StartTlsResponse) ctx
+                       // .extendedOperation(new StartTlsRequest());
+                       // tls.negotiate();
+                       Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
+                       if (securityAuthentication != null)
+                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
+                       else
+                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
+                       Object principal = properties.get(Context.SECURITY_PRINCIPAL);
+                       if (principal != null) {
+                               initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
+                               Object creds = properties.get(Context.SECURITY_CREDENTIALS);
+                               if (creds != null) {
+                                       initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot connect to LDAP", e);
+               }
+
+       }
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+               try {
+                       // tls.close();
+                       initialLdapContext.close();
+                       initialLdapContext = null;
+               } catch (NamingException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       protected InitialLdapContext getLdapContext() {
+               return initialLdapContext;
+       }
+
+       protected void reconnect() throws NamingException {
+               initialLdapContext.reconnect(initialLdapContext.getConnectControls());
+       }
+
+       public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
+                       SearchControls searchControls) throws NamingException {
+               NamingEnumeration<SearchResult> results;
+               try {
+                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
+               } catch (CommunicationException e) {
+                       reconnect();
+                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
+               }
+               return results;
+       }
+
+       public synchronized Attributes getAttributes(LdapName name) throws NamingException {
+               try {
+                       return getLdapContext().getAttributes(name);
+               } catch (CommunicationException e) {
+                       reconnect();
+                       return getLdapContext().getAttributes(name);
+               }
+       }
+
+       synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException {
+               // make sure connection will work
+               reconnect();
+
+               // delete
+               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                       if (!entryExists(dn))
+                               throw new UserDirectoryException("User to delete no found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewUsers().keySet()) {
+                       if (entryExists(dn))
+                               throw new UserDirectoryException("User to create found " + dn);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                       if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
+                               throw new UserDirectoryException("User to modify not found " + dn);
+               }
+
+       }
+
+       protected boolean entryExists(LdapName dn) throws NamingException {
+               try {
+                       return getAttributes(dn).size() != 0;
+               } catch (NameNotFoundException e) {
+                       return false;
+               }
+       }
+
+       synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException {
+               // delete
+               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                       getLdapContext().destroySubcontext(dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewUsers().keySet()) {
+                       DirectoryUser user = wc.getNewUsers().get(dn);
+                       getLdapContext().createSubcontext(dn, user.getAttributes());
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+                       getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
+               }
+       }
+}
index 22c178ef473916f6597ab471d8f69dd7dbb36b7a..2d9a8746973ebb74359b7fe5ff42dd26811b4058 100644 (file)
@@ -4,9 +4,9 @@ import static org.argeo.naming.LdapAttrs.objectClass;
 
 import java.util.ArrayList;
 import java.util.Dictionary;
-import java.util.Hashtable;
 import java.util.List;
 
+import javax.naming.AuthenticationNotSupportedException;
 import javax.naming.Binding;
 import javax.naming.Context;
 import javax.naming.InvalidNameException;
@@ -15,14 +15,11 @@ 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.SearchControls;
 import javax.naming.directory.SearchResult;
-import javax.naming.ldap.InitialLdapContext;
 import javax.naming.ldap.LdapName;
 import javax.transaction.TransactionManager;
 
-import org.argeo.naming.LdapAttrs;
 import org.osgi.framework.Filter;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
@@ -32,53 +29,19 @@ import org.osgi.service.useradmin.User;
  * and an open transaction for write access.
  */
 public class LdapUserAdmin extends AbstractUserDirectory {
-       private InitialLdapContext initialLdapContext = null;
-
-//     private LdapName adminUserDn = null;
-//     private LdifUser adminUser = null;
+       private LdapConnection ldapConnection;
 
        public LdapUserAdmin(Dictionary<String, ?> properties) {
-               super(null, properties);
-               try {
-                       Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
-                       connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
-                       connEnv.put(Context.PROVIDER_URL, getUri().toString());
-                       connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
+               this(properties, false);
+       }
 
-                       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());
-//                             adminUserDn = new LdapName(principal.toString());
-//                             BasicAttributes adminUserAttrs = new BasicAttributes();
-//                             adminUser = new LdifUser(this, adminUserDn, adminUserAttrs);
-                               Object creds = properties.get(Context.SECURITY_CREDENTIALS);
-                               if (creds != null) {
-                                       initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
-//                                     adminUserAttrs.put(LdapAttrs.userPassword.name(), adminUser.hash(creds.toString().toCharArray()));
-                               }
-//                             adminUserAttrs.put(LdapAttrs.memberOf.name(), "cn=admin,ou=roles,ou=node");
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot connect to LDAP", e);
-               }
+       public LdapUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
+               super(null, properties, scoped);
+               ldapConnection = new LdapConnection(getUri().toString(), properties);
        }
 
        public void destroy() {
-               try {
-                       // tls.close();
-                       initialLdapContext.close();
-               } catch (NamingException e) {
-                       e.printStackTrace();
-               }
+               ldapConnection.destroy();
        }
 
        @Override
@@ -97,12 +60,12 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                } else {
                        properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
                }
-               return new LdapUserAdmin(properties);
+               return new LdapUserAdmin(properties, true);
        }
 
-       protected InitialLdapContext getLdapContext() {
-               return initialLdapContext;
-       }
+//     protected InitialLdapContext getLdapContext() {
+//             return initialLdapContext;
+//     }
 
        @Override
        protected Boolean daoHasRole(LdapName dn) {
@@ -116,7 +79,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        @Override
        protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
                try {
-                       Attributes attrs = getLdapContext().getAttributes(name);
+                       Attributes attrs = ldapConnection.getAttributes(name);
                        if (attrs.size() == 0)
                                return null;
                        int roleType = roleType(name);
@@ -129,9 +92,6 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                                throw new UserDirectoryException("Unsupported LDAP type for " + name);
                        return res;
                } catch (NameNotFoundException e) {
-//                     if (adminUserDn != null && adminUserDn.equals(name)) {
-//                             return adminUser;
-//                     }
                        throw e;
                } catch (NamingException e) {
                        return null;
@@ -149,7 +109,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
 
                        LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
 
                        results: while (results.hasMoreElements()) {
                                SearchResult searchResult = results.next();
@@ -170,9 +130,12 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                                res.add(role);
                        }
                        return res;
-//             } catch (NameNotFoundException e) {
-//                     return res;
+               } catch (AuthenticationNotSupportedException e) {
+                       // ignore (typically an unsupported anonymous bind)
+                       // TODO better logging
+                       return res;
                } catch (Exception e) {
+                       e.printStackTrace();
                        throw new UserDirectoryException("Cannot get roles for filter " + f, e);
                }
        }
@@ -192,7 +155,7 @@ public class LdapUserAdmin extends AbstractUserDirectory {
                        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
 
                        LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
 
                        while (results.hasMoreElements()) {
                                SearchResult searchResult = (SearchResult) results.nextElement();
@@ -207,52 +170,16 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        @Override
        protected void prepare(UserDirectoryWorkingCopy wc) {
                try {
-                       getLdapContext().reconnect(getLdapContext().getConnectControls());
-                       // delete
-                       for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                               if (!entryExists(dn))
-                                       throw new UserDirectoryException("User to delete no found " + dn);
-                       }
-                       // add
-                       for (LdapName dn : wc.getNewUsers().keySet()) {
-                               if (entryExists(dn))
-                                       throw new UserDirectoryException("User to create found " + dn);
-                       }
-                       // modify
-                       for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                               if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
-                                       throw new UserDirectoryException("User to modify not found " + dn);
-                       }
+                       ldapConnection.prepareChanges(wc);
                } catch (NamingException e) {
                        throw new UserDirectoryException("Cannot prepare LDAP", e);
                }
        }
 
-       private boolean entryExists(LdapName dn) throws NamingException {
-               try {
-                       return getLdapContext().getAttributes(dn).size() != 0;
-               } catch (NameNotFoundException e) {
-                       return false;
-               }
-       }
-
        @Override
        protected void commit(UserDirectoryWorkingCopy wc) {
                try {
-                       // delete
-                       for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                               getLdapContext().destroySubcontext(dn);
-                       }
-                       // add
-                       for (LdapName dn : wc.getNewUsers().keySet()) {
-                               DirectoryUser user = wc.getNewUsers().get(dn);
-                               getLdapContext().createSubcontext(dn, user.getAttributes());
-                       }
-                       // modify
-                       for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                               Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
-                               getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
-                       }
+                       ldapConnection.commitChanges(wc);
                } catch (NamingException e) {
                        throw new UserDirectoryException("Cannot commit LDAP", e);
                }
index 970ab7162949aa5ad82e12fcf3727679476785c9..832e8e57819a87eaf72f261057615a1c312a544d 100644 (file)
@@ -9,6 +9,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Dictionary;
@@ -40,15 +41,19 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
 
        public LdifUserAdmin(String uri, String baseDn) {
-               this(fromUri(uri, baseDn));
+               this(fromUri(uri, baseDn), false);
        }
 
        public LdifUserAdmin(Dictionary<String, ?> properties) {
-               super(null, properties);
+               this(properties, false);
+       }
+
+       protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
+               super(null, properties, scoped);
        }
 
        public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
-               super(uri, properties);
+               super(uri, properties, false);
        }
 
        @Override
@@ -69,7 +74,7 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                }
                Dictionary<String, Object> properties = cloneProperties();
                properties.put(UserAdminConf.readOnly.name(), "true");
-               LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties);
+               LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
                scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups);
                scopedUserAdmin.users = Collections.unmodifiableSortedMap(users);
                return scopedUserAdmin;
@@ -83,13 +88,15 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        }
 
        public void init() {
+
                try {
-                       if (getUri().getScheme().equals("file")) {
-                               File file = new File(getUri());
+                       URI u = new URI(getUri());
+                       if (u.getScheme().equals("file")) {
+                               File file = new File(u);
                                if (!file.exists())
                                        return;
                        }
-                       load(getUri().toURL().openStream());
+                       load(u.toURL().openStream());
                } catch (Exception e) {
                        throw new UserDirectoryException("Cannot open URL " + getUri(), e);
                }
@@ -100,9 +107,9 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                        throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set");
                if (isReadOnly())
                        throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only");
-               try (FileOutputStream out = new FileOutputStream(new File(getUri()))) {
+               try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) {
                        save(out);
-               } catch (IOException e) {
+               } catch (IOException | URISyntaxException e) {
                        throw new UserDirectoryException("Cannot save user admin to " + getUri(), e);
                }
        }
index fd7826303edb77ed951e2cfb34774193455c16e0..fe1ca7643f1a4ecf1596beeb870e9efdc21f7a2d 100644 (file)
@@ -21,7 +21,7 @@ public class OsUserDirectory extends AbstractUserDirectory {
        private final LdifUser osUser;
 
        public OsUserDirectory(URI uriArg, Dictionary<String, ?> props) {
-               super(uriArg, props);
+               super(uriArg, props, false);
                try {
                        osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn());
                        Attributes attributes = new BasicAttributes();
@@ -53,7 +53,7 @@ public class OsUserDirectory extends AbstractUserDirectory {
        @Override
        protected List<DirectoryUser> doGetRoles(Filter f) {
                List<DirectoryUser> res = new ArrayList<>();
-               if (f==null || f.match(osUser.getProperties()))
+               if (f == null || f.match(osUser.getProperties()))
                        res.add(osUser);
                return res;
        }
index 001d8e8bdea116e51a7a1a304cd4f71820ba17c4..ec41978dc3c441cb7f47ddc52cc392b902c23ca5 100644 (file)
@@ -1,6 +1,5 @@
 package org.argeo.osgi.useradmin;
 
-import java.io.IOException;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -11,10 +10,8 @@ import java.util.List;
 import java.util.Map;
 
 import javax.naming.Context;
-import javax.naming.NamingException;
 import javax.naming.ldap.LdapName;
 
-import org.argeo.naming.DnsBrowser;
 import org.argeo.naming.NamingUtils;
 
 /** Properties used to configure user admins. */
@@ -49,6 +46,7 @@ public enum UserAdminConf {
        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";
@@ -147,8 +145,8 @@ public enum UserAdminConf {
                        URI u = new URI(uriStr);
                        String scheme = u.getScheme();
                        if (scheme != null && scheme.equals(SCHEME_IPA)) {
-                               u = convertIpaConfig(u);
-                               scheme = u.getScheme();
+                               return IpaUtils.convertIpaUri(u);
+//                             scheme = u.getScheme();
                        }
                        String path = u.getPath();
                        // base DN
@@ -166,7 +164,7 @@ public enum UserAdminConf {
                        String principal = null;
                        String credentials = null;
                        if (scheme != null)
-                               if (scheme.equals(SCHEME_LDAP) || scheme.equals("ldaps")) {
+                               if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
                                        // TODO additional checks
                                        if (u.getUserInfo() != null) {
                                                String[] userInfo = u.getUserInfo().split(":");
@@ -210,49 +208,6 @@ public enum UserAdminConf {
                }
        }
 
-       private static URI convertIpaConfig(URI uri) {
-               String path = uri.getPath();
-               String kerberosRealm;
-               if (path == null || path.length() <= 1) {
-                       kerberosRealm = kerberosDomainFromDns();
-               } else {
-                       kerberosRealm = path.substring(1);
-               }
-
-               if (kerberosRealm == null)
-                       throw new UserDirectoryException("No Kerberos domain available for " + uri);
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       String ldapHostsStr = uri.getHost();
-                       if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
-                               List<String> ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase());
-                               if (ldapHosts == null || ldapHosts.size() == 0) {
-                                       throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
-                               } else {
-                                       ldapHostsStr = ldapHosts.get(0);
-                               }
-                       }
-                       URI convertedUri = new URI(
-                                       SCHEME_LDAP + "://" + ldapHostsStr + "/" + IpaUtils.domainToUserDirectoryConfigPath(kerberosRealm));
-                       return convertedUri;
-               } catch (NamingException | IOException | URISyntaxException e) {
-                       throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
-               }
-       }
-
-       private static String kerberosDomainFromDns() {
-               String kerberosDomain;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       String hostname = localhost.getHostName();
-                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       return kerberosDomain;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
-               }
-
-       }
-
        private static String getBaseDnFromHostname() {
                String hostname;
                try {
index 525176d962fcbb407b8c52db8eaf6e99e5c4b9c1..1630b6bd36befa841d3968b84d260750dda8e017 100644 (file)
@@ -62,6 +62,7 @@ class WcXaResource implements XAResource {
                try {
                        userDirectory.prepare(wc);
                } catch (Exception e) {
+                       e.printStackTrace();
                        throw new XAException(XAException.XAER_RMERR);
                }
                return XA_OK;
@@ -78,6 +79,7 @@ class WcXaResource implements XAResource {
                                userDirectory.prepare(wc);
                        userDirectory.commit(wc);
                } catch (Exception e) {
+                       e.printStackTrace();
                        throw new XAException(XAException.XAER_RMERR);
                } finally {
                        cleanUp(xid);
@@ -90,6 +92,7 @@ class WcXaResource implements XAResource {
                        checkXid(xid);
                        userDirectory.rollback(wc(xid));
                } catch (Exception e) {
+                       e.printStackTrace();
                        throw new XAException(XAException.XAER_RMERR);
                } finally {
                        cleanUp(xid);
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java
new file mode 100644 (file)
index 0000000..7cb5863
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.osgi.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class FilterRequirement implements Requirement {
+       private String namespace;
+       private String filter;
+       
+       
+
+       public FilterRequirement(String namespace, String filter) {
+               this.namespace = namespace;
+               this.filter = filter;
+       }
+
+       @Override
+       public Resource getResource() {
+               return null;
+       }
+
+       @Override
+       public String getNamespace() {
+               return namespace;
+       }
+
+       @Override
+       public Map<String, String> getDirectives() {
+               Map<String, String> directives = new HashMap<>();
+               directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+               return directives;
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return new HashMap<>();
+       }
+
+}
index 01836c4842f6560398b775d8fe0696fc95387dd7..d499d3059512073ca82784eae5d84a79f2b6e11d 100644 (file)
@@ -2,6 +2,6 @@
 <classpath>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index 31077737e8e7e789335e263409b2279d190b1b27..72e325d35a40c40ad22a712ae7561fd33ae6ed87 100644 (file)
@@ -902,6 +902,8 @@ public class Jcr {
        // QUERY
        /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
        public static Query createQuery(QueryManager qm, String sql, Object... args) {
+               // fix single quotes
+               sql = sql.replaceAll("'", "''");
                String query = MessageFormat.format(sql, args);
                try {
                        return qm.createQuery(query, Query.JCR_SQL2);
index 56d959e08579c2306e662173227ae1a926c69e02..3be8be184b25f269d581d09f2bf541980883143d 100644 (file)
@@ -38,6 +38,7 @@ import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -1729,4 +1730,49 @@ public class JcrUtils {
                return new String(hexChars);
        }
 
+       /** Export a subtree as a compact XML without namespaces. */
+       public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException {
+               sb.append('<');
+               String nodeName = node.getName();
+               int colIndex = nodeName.indexOf(':');
+               if (colIndex > 0) {
+                       nodeName = nodeName.substring(colIndex + 1);
+               }
+               sb.append(nodeName);
+               PropertyIterator pit = node.getProperties();
+               properties: while (pit.hasNext()) {
+                       Property p = pit.nextProperty();
+                       // skip multiple properties
+                       if (p.isMultiple())
+                               continue properties;
+                       String propertyName = p.getName();
+                       int pcolIndex = propertyName.indexOf(':');
+                       // skip properties with namespaces
+                       if (pcolIndex > 0)
+                               continue properties;
+                       // skip binaries
+                       if (p.getType() == PropertyType.BINARY) {
+                               continue properties;
+                               // TODO retrieve identifier?
+                       }
+                       sb.append(' ');
+                       sb.append(propertyName);
+                       sb.append('=');
+                       sb.append('\"').append(p.getString()).append('\"');
+               }
+
+               if (node.hasNodes()) {
+                       sb.append('>');
+                       NodeIterator children = node.getNodes();
+                       while (children.hasNext()) {
+                               toSimpleXml(children.nextNode(), sb);
+                       }
+                       sb.append("</");
+                       sb.append(nodeName);
+                       sb.append('>');
+               } else {
+                       sb.append("/>");
+               }
+       }
+
 }
index 8f1ee9fa9a4f67752dd1661c460f08cb662b72d3..666b2593e5992dbb980f8e12e43c194fddb1f5c4 100644 (file)
@@ -30,22 +30,33 @@ public class JcrxApi {
         * XML
         */
        /**
-        * Set as a subnode which will be exported as an XML element.
+        * Get the XML text of this child node.
         */
        public static String getXmlValue(Node node, String name) {
                try {
                        if (!node.hasNode(name))
                                return null;
                        Node child = node.getNode(name);
-                       if (!child.hasNode(Jcr.JCR_XMLTEXT))
+                       return getXmlValue(child);
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Cannot get " + name + " as XML text", e);
+               }
+       }
+
+       /**
+        * Get the XML text of this node.
+        */
+       public static String getXmlValue(Node node) {
+               try {
+                       if (!node.hasNode(Jcr.JCR_XMLTEXT))
                                return null;
-                       Node xmlText = child.getNode(Jcr.JCR_XMLTEXT);
+                       Node xmlText = node.getNode(Jcr.JCR_XMLTEXT);
                        if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS))
                                throw new IllegalArgumentException(
                                                "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property");
                        return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
                } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get " + name + " as XML text", e);
+                       throw new IllegalStateException("Cannot get " + node + " as XML text", e);
                }
        }
 
index eca7bdba8f03f22510b7980a94dbfe10c16c0901..e801ebfb4680123285c15553dc70584276fe0057 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index c756bd8e048447bba544d725ca39f0e61972cad6..6003d638ddadab47a630d694903704ca62dce2c4 100644 (file)
@@ -159,25 +159,27 @@ public abstract class AbstractMaintenanceService {
        }
 
        /** Add a user or group to a group. */
-       protected void addToGroup(String roledDn, String groupDn) {
-               if (roledDn.contentEquals(groupDn)) {
+       protected void addToGroup(String groupToAddDn, String groupDn) {
+               if (groupToAddDn.contentEquals(groupDn)) {
                        if (log.isTraceEnabled())
                                log.trace("Ignore adding group " + groupDn + " to itself");
                        return;
                }
 
                if (getUserAdmin() == null) {
-                       log.warn("No user admin service available, cannot add group " + roledDn + " to " + groupDn);
+                       log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn);
                        return;
                }
-               Group managerGroup = (Group) getUserAdmin().getRole(roledDn);
+               Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn);
+               if (groupToAdd == null)
+                       throw new IllegalArgumentException("Group " + groupToAddDn + " not found");
                Group group = (Group) getUserAdmin().getRole(groupDn);
                if (group == null)
                        throw new IllegalArgumentException("Group " + groupDn + " not found");
                try {
                        getUserTransaction().begin();
-                       if (group.addMember(managerGroup))
-                               log.info("Added " + roledDn + " to " + group);
+                       if (group.addMember(groupToAdd))
+                               log.info("Added " + groupToAddDn + " to " + group);
                        getUserTransaction().commit();
                } catch (Exception e) {
                        try {
@@ -185,7 +187,7 @@ public abstract class AbstractMaintenanceService {
                        } catch (Exception e1) {
                                // silent
                        }
-                       throw new IllegalStateException("Cannot add " + managerGroup + " to " + group);
+                       throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn);
                }
        }
 
index a8a298a3c9ab51ff33cfa1e4ec0cb15c79438bd1..190db038cc752af7297f3b2e7637870b72442181 100644 (file)
@@ -3,6 +3,6 @@
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="ext/test"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>