Merge tag 'v2.3.28' into testing
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 16 Mar 2024 07:05:56 +0000 (08:05 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 16 Mar 2024 07:05:56 +0000 (08:05 +0100)
180 files changed:
Makefile
Makefile-rcp.mk
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/QualifiedData.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java
org.argeo.api.acr/src/org/argeo/api/acr/StructuredData.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapNameUtils.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
org.argeo.api.cli/.classpath [deleted file]
org.argeo.api.cli/.project [deleted file]
org.argeo.api.cli/bnd.bnd [deleted file]
org.argeo.api.cli/build.properties [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/package-info.java [deleted file]
org.argeo.api.cms/META-INF/.gitignore [deleted file]
org.argeo.api.cms/bnd.bnd
org.argeo.api.cms/src/org/argeo/api/cms/auth/ImpliedByPrincipal.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/auth/RoleNameUtils.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/auth/SystemRole.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsRole.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java
org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java
org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/APM.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/DirectLibuuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/LibuuidFactory.java
org.argeo.cms.cli/bnd.bnd
org.argeo.cms.ee/bnd.bnd
org.argeo.cms.jshell/src/org/argeo/internal/cms/jshell/osgi/OsgiExecutionControlProvider.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
org.argeo.cms.lib.sshd/build.properties
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentCmsEditable.java
org.argeo.cms/OSGI-INF/cmsState.xml [deleted file]
org.argeo.cms/OSGI-INF/cmsUuidFactory.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/uuidFactory.xml [deleted file]
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/api/cli/CommandArgsException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/CommandRuntimeException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/CommandsCli.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/DescribedCommand.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/HelpCommand.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/PrintHelpRequestException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/package-info.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CurrentUser.java
org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/SystemRole.java [deleted file]
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java [deleted file]
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
org.argeo.cms/src/org/argeo/cms/acr/MountManager.java
org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java
org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java
org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java
org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/CmsSystemRole.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java
org.argeo.cms/src/org/argeo/cms/http/server/StaticHttpHandler.java
org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java
org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java
org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiAuthorization.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiGroup.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiRole.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiUser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java
org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java
org.argeo.cms/src/org/argeo/cms/util/LangUtils.java
org.argeo.init/bnd.bnd
org.argeo.init/src/org/argeo/api/a2/A2Branch.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/A2Component.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/A2Contribution.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/A2Exception.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/A2Module.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/A2Source.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/FsA2Source.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/FsM2Source.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/OsgiContext.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/a2/package-info.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/init/InitConstants.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/init/RuntimeContext.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/api/init/RuntimeManager.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/RuntimeContext.java [deleted file]
org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/Service.java [deleted file]
org.argeo.init/src/org/argeo/init/ServiceMain.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java
org.argeo.init/src/org/argeo/init/SysInitMain.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Branch.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/A2Component.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/A2Contribution.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/A2Exception.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/A2Module.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/A2Source.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/FsA2Source.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/FsM2Source.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/OsgiContext.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java [deleted file]
org.argeo.init/src/org/argeo/init/a2/package-info.java [deleted file]
org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java
org.argeo.init/src/org/argeo/init/logging/ThinLogging.java
org.argeo.init/src/org/argeo/init/osgi/Activator.java
org.argeo.init/src/org/argeo/init/osgi/AdminThread.java [deleted file]
org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java
org.argeo.init/src/org/argeo/init/osgi/DistributionBundle.java
org.argeo.init/src/org/argeo/init/osgi/Launcher.java [deleted file]
org.argeo.init/src/org/argeo/init/osgi/Main.java [deleted file]
org.argeo.init/src/org/argeo/init/osgi/NodeRunner.java [deleted file]
org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java
org.argeo.init/src/org/argeo/init/osgi/OsgiBootConstants.java [deleted file]
org.argeo.init/src/org/argeo/init/osgi/OsgiBootDiagnostics.java
org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java [deleted file]
org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java
org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java
org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/internal/init/InternalState.java [new file with mode: 0644]
sdk/argeo-build
sdk/branches/unstable.bnd
swt/org.argeo.swt.minidesktop/.project
swt/org.argeo.swt.minidesktop/OSGI-INF/miniDesktop.xml [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/bnd.bnd
swt/org.argeo.swt.minidesktop/build.properties
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java
swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/internal/swt/specific/osgi/SwtSpecificRcpActivator.java [new file with mode: 0644]

index 9011b2dc0aa267d56969f17b19c082368e37144b..497de15b0e1e2a03e0dc816b00669a25920a1fff 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,4 @@
 include sdk.mk
-.PHONY: clean all osgi
-
-all: osgi
-       $(MAKE) -f Makefile-rcp.mk all
-       
-install: osgi-install
-
-uninstall: osgi-uninstall
 
 A2_CATEGORY = org.argeo.cms
 
@@ -15,7 +7,6 @@ org.argeo.init \
 org.argeo.api.uuid \
 org.argeo.api.register \
 org.argeo.api.acr \
-org.argeo.api.cli \
 org.argeo.api.cms \
 org.argeo.cms \
 org.argeo.cms.ux \
@@ -49,10 +40,18 @@ org.argeo.api.uuid \
 org.argeo.api.acr \
 org.argeo.api.cms
 
-clean:
-       rm -rf $(BUILD_BASE)
+all: osgi-all
+       $(MAKE) -f Makefile-rcp.mk all
+       
+clean: osgi-clean
        $(MAKE) -f Makefile-rcp.mk clean
 
-A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES)))
+install: osgi-install
+       $(MAKE) -f Makefile-rcp.mk install
+
+uninstall: osgi-uninstall
+       $(MAKE) -f Makefile-rcp.mk uninstall
+
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
 
-include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
+.PHONY: clean all install uninstall
index 1fb93728c82474391695677a9bbce12c52a90d05..a9e262843550f864904cdc22dcc0c40c6275a5c1 100644 (file)
@@ -1,7 +1,4 @@
 include sdk.mk
-.PHONY: clean all osgi
-
-all: osgi 
 
 A2_CATEGORY = org.argeo.cms
 
@@ -19,9 +16,16 @@ osgi/api/org.argeo.tp.osgi \
 swt/rcp/org.argeo.tp.swt \
 lib/linux/x86_64/swt/rcp/org.argeo.tp.swt \
 
-clean:
-       rm -rf $(BUILD_BASE)
-
 VPATH = .:swt/rcp
 
-include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
+all: osgi-all
+
+clean: osgi-clean
+       
+install: osgi-install
+
+uninstall: osgi-uninstall
+
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
+
+.PHONY: clean all install uninstall
index 7ec29594713e885d8f39a2efa00c0a24b186f7f3..c69e76a193077207091e58ad0fbc4a7d8f16eaa1 100644 (file)
@@ -4,7 +4,6 @@ import static org.argeo.api.acr.NamespaceUtils.unqualified;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -13,77 +12,21 @@ import java.util.concurrent.CompletableFuture;
 import javax.xml.namespace.QName;
 
 /**
- * A semi-structured content, with attributes, within a hierarchical structure.
+ * A semi-structured content, with attributes, within a hierarchical structure
+ * whose nodes are named.
  */
-public interface Content extends Iterable<Content>, Map<QName, Object> {
-       /** The base of a repository path. */
-       String ROOT_PATH = "/";
+public interface Content extends QualifiedData<Content> {
+       /** The path separator: '/' */
+       char PATH_SEPARATOR = '/';
 
-       QName getName();
+       /** The base of a repository path. */
+       String ROOT_PATH = Character.toString(PATH_SEPARATOR);
 
        String getPath();
 
-       Content getParent();
-
-       /*
-        * ATTRIBUTES OPERATIONS
-        */
-
-       <A> Optional<A> get(QName key, Class<A> clss);
-
-       Class<?> getType(QName key);
-
-       boolean isMultiple(QName key);
-
-       <A> List<A> getMultiple(QName key, Class<A> clss);
-
-       /*
-        * ATTRIBUTES OPERATION HELPERS
-        */
-       default boolean containsKey(QNamed key) {
-               return containsKey(key.qName());
-       }
-
-       default <A> Optional<A> get(QNamed key, Class<A> clss) {
-               return get(key.qName(), clss);
-       }
-
-       default Object get(QNamed key) {
-               return get(key.qName());
-       }
-
-       default Object put(QNamed key, Object value) {
-               return put(key.qName(), value);
-       }
-
-       default Object remove(QNamed key) {
-               return remove(key.qName());
-       }
-
-       // TODO do we really need the helpers below?
-
-       default Object get(String key) {
-               return get(unqualified(key));
-       }
-
-       default Object put(String key, Object value) {
-               return put(unqualified(key), value);
-       }
-
-       default Object remove(String key) {
-               return remove(unqualified(key));
-       }
-
-       @SuppressWarnings("unchecked")
-       default <A> List<A> getMultiple(QName key) {
-               Class<A> type;
-               try {
-                       type = (Class<A>) getType(key);
-               } catch (ClassCastException e) {
-                       throw new IllegalArgumentException("Requested type is not the default type");
-               }
-               List<A> res = getMultiple(key, type);
-               return res;
+       /** MUST be {@link Content#PATH_SEPARATOR}. */
+       default char getPathSeparator() {
+               return PATH_SEPARATOR;
        }
 
        /*
@@ -164,21 +107,9 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return res;
        }
 
-       /*
-        * SIBLINGS
-        */
-
-       default int getSiblingIndex() {
-               return 1;
-       }
-
        /*
         * DEFAULT METHODS
         */
-       default <A> A adapt(Class<A> clss) {
-               throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
-       }
-
        default <C extends Closeable> C open(Class<C> clss) throws IOException {
                throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName());
        }
@@ -190,19 +121,6 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
        /*
         * CHILDREN
         */
-
-       default boolean hasChild(QName name) {
-               for (Content child : this) {
-                       if (child.getName().equals(name))
-                               return true;
-               }
-               return false;
-       }
-
-       default boolean hasChild(QNamed name) {
-               return hasChild(name.qName());
-       }
-
        default Content anyOrAddChild(QName name, QName... classes) {
                Content child = anyChild(name);
                if (child != null)
@@ -214,92 +132,10 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                return anyOrAddChild(unqualified(name), classes);
        }
 
-       /** Any child with this name, or null if there is none */
-       default Content anyChild(QName name) {
-               for (Content child : this) {
-                       if (child.getName().equals(name))
-                               return child;
-               }
-               return null;
-       }
-
-       default List<Content> children(QName name) {
-               List<Content> res = new ArrayList<>();
-               for (Content child : this) {
-                       if (child.getName().equals(name))
-                               res.add(child);
-               }
-               return res;
-       }
-
-       default List<Content> children(QNamed name) {
-               return children(name.qName());
-       }
-
-       default Optional<Content> soleChild(QNamed name) {
-               return soleChild(name.qName());
-       }
-
-       default Optional<Content> soleChild(QName name) {
-               List<Content> res = children(name);
-               if (res.isEmpty())
-                       return Optional.empty();
-               if (res.size() > 1)
-                       throw new IllegalStateException(this + " has multiple children with name " + name);
-               return Optional.of(res.get(0));
-       }
-
        default Content soleOrAddChild(QName name, QName... classes) {
                return soleChild(name).orElseGet(() -> this.add(name, classes));
        }
 
-       default Content child(QName name) {
-               return soleChild(name).orElseThrow();
-       }
-
-       default Content child(QNamed name) {
-               return child(name.qName());
-       }
-
-       /*
-        * ATTR AS STRING
-        */
-       /**
-        * Convenience method returning an attribute as a {@link String}.
-        * 
-        * @param key the attribute name
-        * @return the attribute value as a {@link String} or <code>null</code>.
-        * 
-        * @see Object#toString()
-        */
-       default String attr(QName key) {
-               return get(key, String.class).orElse(null);
-       }
-
-       /**
-        * Convenience method returning an attribute as a {@link String}.
-        * 
-        * @param key the attribute name
-        * @return the attribute value as a {@link String} or <code>null</code>.
-        * 
-        * @see Object#toString()
-        */
-       default String attr(QNamed key) {
-               return attr(key.qName());
-       }
-
-       /**
-        * Convenience method returning an attribute as a {@link String}.
-        * 
-        * @param key the attribute name
-        * @return the attribute value as a {@link String} or <code>null</code>.
-        * 
-        * @see Object#toString()
-        */
-       default String attr(String key) {
-               return attr(unqualified(key));
-       }
-
        /*
         * CONTEXT
         */
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/QualifiedData.java b/org.argeo.api.acr/src/org/argeo/api/acr/QualifiedData.java
new file mode 100644 (file)
index 0000000..b2c7bbb
--- /dev/null
@@ -0,0 +1,187 @@
+package org.argeo.api.acr;
+
+import static org.argeo.api.acr.NamespaceUtils.unqualified;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.xml.namespace.QName;
+
+/** A {@link StructuredData} whose attributes have qualified keys. */
+public interface QualifiedData<CHILD extends QualifiedData<CHILD>> extends StructuredData<QName, Object, CHILD> {
+       QName getName();
+
+       CHILD getParent();
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+
+       <A> Optional<A> get(QName key, Class<A> clss);
+
+       Class<?> getType(QName key);
+
+       boolean isMultiple(QName key);
+
+       <A> List<A> getMultiple(QName key, Class<A> clss);
+
+       /*
+        * PATH
+        */
+       char getPathSeparator();
+
+       /*
+        * ATTRIBUTES OPERATION HELPERS
+        */
+       default boolean containsKey(QNamed key) {
+               return containsKey(key.qName());
+       }
+
+       default <A> Optional<A> get(QNamed key, Class<A> clss) {
+               return get(key.qName(), clss);
+       }
+
+       default Object get(QNamed key) {
+               return get(key.qName());
+       }
+
+       default Object put(QNamed key, Object value) {
+               return put(key.qName(), value);
+       }
+
+       default Object remove(QNamed key) {
+               return remove(key.qName());
+       }
+
+       // TODO do we really need the helpers below?
+
+       default Object get(String key) {
+               return get(unqualified(key));
+       }
+
+       default Object put(String key, Object value) {
+               return put(unqualified(key), value);
+       }
+
+       default Object remove(String key) {
+               return remove(unqualified(key));
+       }
+
+       @SuppressWarnings("unchecked")
+       default <A> List<A> getMultiple(QName key) {
+               Class<A> type;
+               try {
+                       type = (Class<A>) getType(key);
+               } catch (ClassCastException e) {
+                       throw new IllegalArgumentException("Requested type is not the default type");
+               }
+               List<A> res = getMultiple(key, type);
+               return res;
+       }
+
+       /*
+        * CHILDREN
+        */
+
+       default boolean hasChild(QName name) {
+               for (CHILD child : this) {
+                       if (child.getName().equals(name))
+                               return true;
+               }
+               return false;
+       }
+
+       default boolean hasChild(QNamed name) {
+               return hasChild(name.qName());
+       }
+
+       /** Any child with this name, or null if there is none */
+       default CHILD anyChild(QName name) {
+               for (CHILD child : this) {
+                       if (child.getName().equals(name))
+                               return child;
+               }
+               return null;
+       }
+
+       default List<CHILD> children(QName name) {
+               List<CHILD> res = new ArrayList<>();
+               for (CHILD child : this) {
+                       if (child.getName().equals(name))
+                               res.add(child);
+               }
+               return res;
+       }
+
+       default List<CHILD> children(QNamed name) {
+               return children(name.qName());
+       }
+
+       default Optional<CHILD> soleChild(QNamed name) {
+               return soleChild(name.qName());
+       }
+
+       default Optional<CHILD> soleChild(QName name) {
+               List<CHILD> res = children(name);
+               if (res.isEmpty())
+                       return Optional.empty();
+               if (res.size() > 1)
+                       throw new IllegalStateException(this + " has multiple children with name " + name);
+               return Optional.of(res.get(0));
+       }
+
+       default CHILD child(QName name) {
+               return soleChild(name).orElseThrow();
+       }
+
+       default CHILD child(QNamed name) {
+               return child(name.qName());
+       }
+
+       /*
+        * ATTR AS STRING
+        */
+       /**
+        * Convenience method returning an attribute as a {@link String}.
+        * 
+        * @param key the attribute name
+        * @return the attribute value as a {@link String} or <code>null</code>.
+        * 
+        * @see Object#toString()
+        */
+       default String attr(QName key) {
+               return get(key, String.class).orElse(null);
+       }
+
+       /**
+        * Convenience method returning an attribute as a {@link String}.
+        * 
+        * @param key the attribute name
+        * @return the attribute value as a {@link String} or <code>null</code>.
+        * 
+        * @see Object#toString()
+        */
+       default String attr(QNamed key) {
+               return attr(key.qName());
+       }
+
+       /**
+        * Convenience method returning an attribute as a {@link String}.
+        * 
+        * @param key the attribute name
+        * @return the attribute value as a {@link String} or <code>null</code>.
+        * 
+        * @see Object#toString()
+        */
+       default String attr(String key) {
+               return attr(unqualified(key));
+       }
+
+       /*
+        * SIBLINGS
+        */
+       default int getSiblingIndex() {
+               return 1;
+       }
+}
index fe50fd0c3b58058c55a16b0c3feef24486f64510..871275a487c2f310139600720a8c3131ae608af2 100644 (file)
@@ -12,13 +12,19 @@ import javax.xml.namespace.NamespaceContext;
 /**
  * Programmatically defined {@link NamespaceContext}, which is valid at runtime
  * (when the software is running). Code contributing namespaces MUST register
- * here with a single default prefix, nad MUST make sure that stored data
- * contains the fully qualified namespace URI.
+ * here with a single default prefix, and MUST make sure that stored data
+ * contains the fully qualified namespace URI.</br>
+ * </br>
+ * All environments sharing the classloader of this class MUST use strictly the
+ * same default prefix / namespace mappings, as a static reference to the
+ * mapping is kept.
  */
 public class RuntimeNamespaceContext implements NamespaceContext {
        public final static String XSD_DEFAULT_PREFIX = "xs";
        public final static String XSD_INSTANCE_DEFAULT_PREFIX = "xsi";
 
+       private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
+
        private NavigableMap<String, String> prefixes = new TreeMap<>();
        private NavigableMap<String, String> namespaces = new TreeMap<>();
 
@@ -54,7 +60,6 @@ public class RuntimeNamespaceContext implements NamespaceContext {
        /*
         * STATIC
         */
-       private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
 
        static {
                // Standard
@@ -62,6 +67,7 @@ public class RuntimeNamespaceContext implements NamespaceContext {
                register(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE);
 
                // Common
+               // FIXME shouldn't it be registered externally?
                register(XMLConstants.W3C_XML_SCHEMA_NS_URI, XSD_DEFAULT_PREFIX);
                register(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XSD_INSTANCE_DEFAULT_PREFIX);
 
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/StructuredData.java b/org.argeo.api.acr/src/org/argeo/api/acr/StructuredData.java
new file mode 100644 (file)
index 0000000..7708232
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.api.acr;
+
+import java.util.Map;
+
+/** A hierarchical structure of unnamed mappings. */
+public interface StructuredData<KEY, VALUE, CHILD> extends Map<KEY, VALUE>, Iterable<CHILD> {
+       /*
+        * DEFAULT METHODS
+        */
+       default <A> A adapt(Class<A> clss) {
+               throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapNameUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapNameUtils.java
new file mode 100644 (file)
index 0000000..72fa65a
--- /dev/null
@@ -0,0 +1,69 @@
+package org.argeo.api.acr.ldap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+       public static LdapName relativeName(LdapName prefix, LdapName dn) {
+               try {
+                       if (!dn.startsWith(prefix))
+                               throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+                       LdapName res = (LdapName) dn.clone();
+                       for (int i = 0; i < prefix.size(); i++) {
+                               res.remove(0);
+                       }
+                       return res;
+               } catch (InvalidNameException e) {
+                       throw new IllegalStateException("Cannot find realtive name", e);
+               }
+       }
+
+       public static LdapName getParent(LdapName dn) {
+               try {
+                       LdapName parent = (LdapName) dn.clone();
+                       parent.remove(parent.size() - 1);
+                       return parent;
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+               }
+       }
+
+       public static Rdn getParentRdn(LdapName dn) {
+               if (dn.size() < 2)
+                       throw new IllegalArgumentException(dn + " has no parent");
+               Rdn parentRdn = dn.getRdn(dn.size() - 2);
+               return parentRdn;
+       }
+
+       public static LdapName toLdapName(String distinguishedName) {
+               try {
+                       return new LdapName(distinguishedName);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+               }
+       }
+
+       public static Rdn getLastRdn(LdapName dn) {
+               return dn.getRdn(dn.size() - 1);
+       }
+
+       public static String getLastRdnAsString(LdapName dn) {
+               return getLastRdn(dn).toString();
+       }
+
+       public static String getLastRdnValue(String dn) {
+               return getLastRdnValue(toLdapName(dn));
+       }
+
+       public static String getLastRdnValue(LdapName dn) {
+               return getLastRdn(dn).getValue().toString();
+       }
+
+       /** singleton */
+       private LdapNameUtils() {
+
+       }
+}
index f1e5aaaa80f24af4dce505a935b342645d9910c8..5e718223601e80330e6617afd2ca04607488ca09 100644 (file)
@@ -41,10 +41,10 @@ public interface ProvidedContent extends Content {
        @Override
        default Optional<Content> getContent(String path) {
                String absolutePath;
-               if (path.startsWith(Content.ROOT_PATH)) {// absolute
+               if (path.startsWith(ROOT_PATH)) {// absolute
                        absolutePath = path;
                } else {// relative
-                       absolutePath = getPath() + '/' + path;
+                       absolutePath = getPath() + PATH_SEPARATOR + path;
                }
                return getSession().exists(absolutePath) ? Optional.of(getSession().get(absolutePath)) : Optional.empty();
        }
diff --git a/org.argeo.api.cli/.classpath b/org.argeo.api.cli/.classpath
deleted file mode 100644 (file)
index 81fe078..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.api.cli/.project b/org.argeo.api.cli/.project
deleted file mode 100644 (file)
index 8f5cd41..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.api.cli</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.api.cli/bnd.bnd b/org.argeo.api.cli/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/org.argeo.api.cli/build.properties b/org.argeo.api.cli/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java
deleted file mode 100644 (file)
index 935247f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.api.cli;
-
-/** Exception thrown when the provided arguments are not correct. */
-public class CommandArgsException extends IllegalArgumentException {
-       private static final long serialVersionUID = -7271050747105253935L;
-       private String commandName;
-       private volatile CommandsCli commandsCli;
-
-       public CommandArgsException(Exception cause) {
-               super(cause.getMessage(), cause);
-       }
-
-       public CommandArgsException(String message) {
-               super(message);
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public void setCommandName(String commandName) {
-               this.commandName = commandName;
-       }
-
-       public CommandsCli getCommandsCli() {
-               return commandsCli;
-       }
-
-       public void setCommandsCli(CommandsCli commandsCli) {
-               this.commandsCli = commandsCli;
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java
deleted file mode 100644 (file)
index 52c0334..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.api.cli;
-
-import java.util.List;
-
-/** {@link RuntimeException} referring during a command run. */
-public class CommandRuntimeException extends RuntimeException {
-       private static final long serialVersionUID = 5595999301269377128L;
-
-       private final DescribedCommand<?> command;
-       private final List<String> arguments;
-
-       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
-               this(null, e, command, arguments);
-       }
-
-       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
-               this(message, null, command, arguments);
-       }
-
-       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
-               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
-                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
-               this.command = command;
-               this.arguments = arguments;
-       }
-
-       public DescribedCommand<?> getCommand() {
-               return command;
-       }
-
-       public List<String> getArguments() {
-               return arguments;
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java
deleted file mode 100644 (file)
index 5bbfcfa..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-package org.argeo.api.cli;
-
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.function.Function;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.MissingOptionException;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-
-/** Base class for a CLI managing sub commands. */
-public abstract class CommandsCli implements DescribedCommand<Object> {
-       private final String commandName;
-       private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
-
-       protected final Options options = new Options();
-
-       public CommandsCli(String commandName) {
-               this.commandName = commandName;
-       }
-
-       @Override
-       public Object apply(List<String> args) {
-               String cmd = null;
-               List<String> newArgs = new ArrayList<>();
-               boolean isHelpOption = false;
-               try {
-                       CommandLineParser clParser = new DefaultParser();
-                       CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
-                       List<String> leftOvers = commonCl.getArgList();
-                       for (String arg : leftOvers) {
-                               if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) {
-                                       isHelpOption = true;
-                                       // TODO break?
-                               }
-
-                               if (!arg.startsWith("-") && cmd == null) {
-                                       cmd = arg;
-                               } else {
-                                       newArgs.add(arg);
-                               }
-                       }
-               } catch (ParseException e) {
-                       CommandArgsException cae = new CommandArgsException(e);
-                       throw cae;
-               }
-
-               Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
-
-               // --help option
-               if (!(function instanceof CommandsCli))
-                       if (function instanceof DescribedCommand<?> command)
-                               if (isHelpOption) {
-                                       throw new PrintHelpRequestException(cmd, this);
-//                                     StringWriter out = new StringWriter();
-//                                     HelpCommand.printHelp(command, out);
-//                                     System.out.println(out.toString());
-//                                     return null;
-                               }
-
-               if (function == null)
-                       throw new IllegalArgumentException("Uknown command " + cmd);
-               try {
-                       Object value = function.apply(newArgs);
-                       return value != null ? value.toString() : null;
-               } catch (CommandArgsException e) {
-                       if (e.getCommandName() == null) {
-                               e.setCommandName(cmd);
-                               e.setCommandsCli(this);
-                       }
-                       throw e;
-               } catch (IllegalArgumentException e) {
-                       CommandArgsException cae = new CommandArgsException(e);
-                       cae.setCommandName(cmd);
-                       throw cae;
-               }
-       }
-
-       @Override
-       public Options getOptions() {
-               return options;
-       }
-
-       protected void addCommand(String cmd, Function<List<String>, ?> function) {
-               commands.put(cmd, function);
-
-       }
-
-       @Override
-       public String getUsage() {
-               return "[command]";
-       }
-
-       protected void addCommandsCli(CommandsCli commandsCli) {
-               addCommand(commandsCli.getCommandName(), commandsCli);
-               commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli));
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public Set<String> getSubCommands() {
-               return commands.keySet();
-       }
-
-       public Function<List<String>, ?> getCommand(String command) {
-               return commands.get(command);
-       }
-
-       public HelpCommand getHelpCommand() {
-               return (HelpCommand) getCommand(HelpCommand.HELP);
-       }
-
-       public Function<List<String>, String> getDefaultCommand() {
-               return getHelpCommand();
-       }
-
-       /** In order to implement quickly a main method. */
-       public static void mainImpl(CommandsCli cli, String[] args) {
-               try {
-                       cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli));
-                       Object output = cli.apply(Arrays.asList(args));
-                       if (output != null)
-                               System.out.println(output);
-                       System.exit(0);
-               } catch (PrintHelpRequestException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
-                       System.out.println(out.toString());
-               } catch (CommandArgsException e) {
-                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
-                       Throwable cause = e.getCause();
-                       if (!(cause instanceof MissingOptionException))
-                               e.printStackTrace();
-                       if (e.getCommandName() != null) {
-                               StringWriter out = new StringWriter();
-                               HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
-                               System.err.println(out.toString());
-                       } else {
-                               e.printStackTrace();
-                       }
-                       System.exit(1);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java
deleted file mode 100644 (file)
index 51cb2ce..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.api.cli;
-
-import java.io.StringWriter;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-
-/** A command that can be described. */
-public interface DescribedCommand<T> extends Function<List<String>, T> {
-       default Options getOptions() {
-               return new Options();
-       }
-
-       String getDescription();
-
-       default String getUsage() {
-               return null;
-       }
-
-       default String getExamples() {
-               return null;
-       }
-
-       default CommandLine toCommandLine(List<String> args) {
-               try {
-                       DefaultParser parser = new DefaultParser();
-                       return parser.parse(getOptions(), args.toArray(new String[args.size()]));
-               } catch (ParseException e) {
-                       throw new CommandArgsException(e);
-               }
-       }
-
-       /** In order to implement quickly a main method. */
-       public static void mainImpl(DescribedCommand<?> command, String[] args) {
-               try {
-                       Object output = command.apply(Arrays.asList(args));
-                       System.out.println(output);
-                       System.exit(0);
-               } catch (PrintHelpRequestException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(command, out);
-                       System.out.println(out.toString());
-                       System.exit(1);
-               } catch (IllegalArgumentException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(command, out);
-                       System.err.println(out.toString());
-                       System.exit(1);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java
deleted file mode 100644 (file)
index cd51d76..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.api.cli;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-
-/** A special command that can describe {@link DescribedCommand}. */
-public class HelpCommand implements DescribedCommand<String> {
-       /**
-        * System property forcing the root command to this value (typically the name of
-        * a script).
-        */
-       public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand";
-
-       final static String HELP = "help";
-       final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build();
-
-       private CommandsCli commandsCli;
-       private CommandsCli parentCommandsCli;
-
-       // Help formatting
-       private static int helpWidth = 80;
-       private static int helpLeftPad = 4;
-       private static int helpDescPad = 20;
-
-       public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
-               super();
-               this.parentCommandsCli = parentCommandsCli;
-               this.commandsCli = commandsCli;
-       }
-
-       @Override
-       public String apply(List<String> args) {
-               StringWriter out = new StringWriter();
-
-               if (args.size() == 0) {// overview
-                       printHelp(commandsCli, out);
-               } else {
-                       String cmd = args.get(0);
-                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
-                       if (function == null)
-                               return "Command " + cmd + " not found.";
-                       Options options;
-                       String examples;
-                       DescribedCommand<?> command = null;
-                       if (function instanceof DescribedCommand) {
-                               command = (DescribedCommand<?>) function;
-                               options = command.getOptions();
-                               examples = command.getExamples();
-                       } else {
-                               options = new Options();
-                               examples = null;
-                       }
-                       String description = getShortDescription(function);
-                       String commandCall = getCommandUsage(cmd, command);
-                       HelpFormatter formatter = new HelpFormatter();
-                       formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
-                                       helpDescPad, examples, false);
-               }
-               return out.toString();
-       }
-
-       private static String getShortDescription(Function<List<String>, ?> function) {
-               if (function instanceof DescribedCommand) {
-                       return ((DescribedCommand<?>) function).getDescription();
-               } else {
-                       return function.toString();
-               }
-       }
-
-       public String getCommandUsage(String cmd, DescribedCommand<?> command) {
-               String commandCall = getCommandCall(commandsCli) + " " + cmd;
-               assert command != null;
-               if (command != null && command.getUsage() != null) {
-                       commandCall = commandCall + " " + command.getUsage();
-               }
-               return commandCall;
-       }
-
-       @Override
-       public String getDescription() {
-               return "Shows this help or describes a command";
-       }
-
-       @Override
-       public String getUsage() {
-               return "[command]";
-       }
-
-       public CommandsCli getParentCommandsCli() {
-               return parentCommandsCli;
-       }
-
-       protected String getCommandCall(CommandsCli commandsCli) {
-               HelpCommand hc = commandsCli.getHelpCommand();
-               if (hc.getParentCommandsCli() != null) {
-                       return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
-               } else {
-                       String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY);
-                       if (rootCommand != null)
-                               return rootCommand;
-                       return commandsCli.getCommandName();
-               }
-       }
-
-       public static void printHelp(DescribedCommand<?> command, StringWriter out) {
-               String usage = "java " + command.getClass().getName()
-                               + (command.getUsage() != null ? " " + command.getUsage() : "");
-               HelpFormatter formatter = new HelpFormatter();
-               Options options = command.getOptions();
-               options.addOption(HelpCommand.HELP_OPTION);
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
-                               helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
-               if (commandName == null) {
-                       printHelp(commandsCli, out);
-                       return;
-               }
-               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
-               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
-               HelpFormatter formatter = new HelpFormatter();
-               Options options = command.getOptions();
-               options.addOption(HelpCommand.HELP_OPTION);
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
-                               helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, StringWriter out) {
-               out.append(commandsCli.getDescription()).append('\n');
-               String leftPad = spaces(helpLeftPad);
-               for (String cmd : commandsCli.getSubCommands()) {
-                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
-                       assert function != null;
-                       out.append(leftPad);
-                       out.append(cmd);
-                       // TODO deal with long commands
-                       out.append(spaces(helpDescPad - cmd.length()));
-                       out.append(getShortDescription(function));
-                       out.append('\n');
-               }
-       }
-
-       private static String spaces(int count) {
-               // Java 11
-               // return " ".repeat(count);
-               if (count <= 0)
-                       return "";
-               else {
-                       StringBuilder sb = new StringBuilder(count);
-                       for (int i = 0; i < count; i++)
-                               sb.append(' ');
-                       return sb.toString();
-               }
-       }
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java b/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java
deleted file mode 100644 (file)
index 017386b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.argeo.api.cli;
-
-/** An exception indicating that help should be printed. */
-class PrintHelpRequestException extends RuntimeException {
-
-       private static final long serialVersionUID = -9029122270660656639L;
-
-       private String commandName;
-       private volatile CommandsCli commandsCli;
-
-       public PrintHelpRequestException(String commandName, CommandsCli commandsCli) {
-               super();
-               this.commandName = commandName;
-               this.commandsCli = commandsCli;
-       }
-
-       public static long getSerialversionuid() {
-               return serialVersionUID;
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public CommandsCli getCommandsCli() {
-               return commandsCli;
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java b/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java
deleted file mode 100644 (file)
index 114fd02..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Command line API. */
-package org.argeo.api.cli;
\ No newline at end of file
diff --git a/org.argeo.api.cms/META-INF/.gitignore b/org.argeo.api.cms/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
index 0b7c5a4da7b489aa09accf8fa7e242298817e1f4..19a9abdf282f3f278db9fc520d205bd1b1fbcaa6 100644 (file)
@@ -1,7 +1,6 @@
 Import-Package: \
 javax.transaction.xa,\
 javax.security.*,\
-org.osgi.service.useradmin,\
 *
 
 Export-Package: org.argeo.api.cms.*
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/auth/ImpliedByPrincipal.java b/org.argeo.api.cms/src/org/argeo/api/cms/auth/ImpliedByPrincipal.java
new file mode 100644 (file)
index 0000000..21a6325
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.api.cms.auth;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+/**
+ * A {@link Principal} which has been implied by an authorisation. If it is
+ * empty it means this is an additional identity, otherwise it lists the users
+ * (typically the logged-in user but possibly empty {@link ImpliedByPrincipal}s)
+ * which have implied it. When an additional identity is removed, the related
+ * {@link ImpliedByPrincipal}s can thus be removed.
+ */
+public final class ImpliedByPrincipal implements Principal {
+       private final String name;
+       private final QName roleName;
+       private final boolean systemRole;
+       private final String context;
+
+       private Set<Principal> causes = new HashSet<Principal>();
+
+       public ImpliedByPrincipal(String name, Principal userPrincipal) {
+               this.name = name;
+               roleName = RoleNameUtils.getLastRdnAsName(name);
+               systemRole = RoleNameUtils.isSystemRole(roleName);
+               context = RoleNameUtils.getContext(name);
+               if (userPrincipal != null)
+                       causes.add(userPrincipal);
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       /*
+        * OBJECT
+        */
+
+       public QName getRoleName() {
+               return roleName;
+       }
+
+       public String getContext() {
+               return context;
+       }
+
+       public boolean isSystemRole() {
+               return systemRole;
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof ImpliedByPrincipal) {
+                       ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
+                       // TODO check members too?
+                       return name.equals(that.name);
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return name.toString();
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/auth/RoleNameUtils.java b/org.argeo.api.cms/src/org/argeo/api/cms/auth/RoleNameUtils.java
new file mode 100644 (file)
index 0000000..52e2380
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.api.cms.auth;
+
+import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapNameUtils;
+
+/** Simplifies analysis of system roles. */
+public class RoleNameUtils {
+       public static String getLastRdnValue(String dn) {
+               return LdapNameUtils.getLastRdnValue(dn);
+//             // we don't use LdapName for portability with Android
+//             // TODO make it more robust
+//             String[] parts = dn.split(",");
+//             String[] rdn = parts[0].split("=");
+//             return rdn[1];
+       }
+
+       public static QName getLastRdnAsName(String dn) {
+               String cn = getLastRdnValue(dn);
+               QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn);
+               return roleName;
+       }
+
+       public static boolean isSystemRole(QName roleName) {
+               return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI);
+       }
+
+       public static String getParent(String dn) {
+               int index = dn.indexOf(',');
+               return dn.substring(index + 1);
+       }
+
+       /** Up two levels. */
+       public static String getContext(String dn) {
+               return getParent(getParent(dn));
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/auth/SystemRole.java b/org.argeo.api.cms/src/org/argeo/api/cms/auth/SystemRole.java
new file mode 100644 (file)
index 0000000..9880851
--- /dev/null
@@ -0,0 +1,47 @@
+package org.argeo.api.cms.auth;
+
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** A programmatic role. */
+public interface SystemRole {
+       QName qName();
+
+       /** Whether this role is implied for this authenticated user. */
+       default boolean implied(Subject subject, String context) {
+               return implied(qName(), subject, context);
+       }
+
+       /** Whether this role is implied for this distinguished name. */
+       default boolean implied(String dn, String context) {
+               String roleContext = RoleNameUtils.getContext(dn);
+               QName roleName = RoleNameUtils.getLastRdnAsName(dn);
+               return roleContext.equalsIgnoreCase(context) && qName().equals(roleName);
+       }
+
+       /**
+        * Whether this role is implied for this authenticated subject. If context is
+        * <code>null</code>, it is not considered; this should be used to build user
+        * interfaces, but not to authorise.
+        */
+       static boolean implied(QName name, Subject subject, String context) {
+               Set<ImpliedByPrincipal> roles = subject.getPrincipals(ImpliedByPrincipal.class);
+               for (ImpliedByPrincipal role : roles) {
+                       if (role.isSystemRole()) {
+                               if (role.getRoleName().equals(name)) {
+                                       // !! if context is not specified, it is considered irrelevant
+                                       if (context == null)
+                                               return true;
+                                       if (role.getContext().equalsIgnoreCase(context)
+                                                       || role.getContext().equals(CmsConstants.NODE_BASEDN))
+                                               return true;
+                               }
+                       }
+               }
+               return false;
+       }
+}
index 5d3a69575b82b1815b03a9637dd0753dc6bbb98a..6aaff8dcd04b2a323f440f2ee92102bab811ca92 100644 (file)
@@ -1,9 +1,7 @@
 package org.argeo.api.cms.directory;
 
-import org.osgi.service.useradmin.Authorization;
-
 /** An authorisation to a CMS system. */
-public interface CmsAuthorization extends Authorization {
+public interface CmsAuthorization {
        /** The role which did imply this role, <code>null</code> if a direct role. */
        default String getImplyingRole(String role) {
                return null;
index 410d391ba71bcc9f0a7c88cc7e4f7ac46828432b..f41ae6f82eb1571f73868a4767f2b4a07e6badc5 100644 (file)
@@ -1,8 +1,8 @@
 package org.argeo.api.cms.directory;
 
-import org.osgi.service.useradmin.Group;
+import java.util.Set;
 
-/** A group in a user directroy. */
-public interface CmsGroup extends Group, CmsUser {
-//     List<LdapName> getMemberNames();
+/** A group in a user directory. */
+public interface CmsGroup extends CmsUser {
+       Set<? extends CmsRole> getDirectMembers();
 }
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsRole.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsRole.java
new file mode 100644 (file)
index 0000000..7060eb0
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.api.cms.directory;
+
+import java.util.Dictionary;
+
+/** Parent of user/group hierarchy */
+public interface CmsRole {
+       String getName();
+
+       // TODO replace with Map or ACR content
+       @Deprecated
+       Dictionary<String, Object> getProperties();
+}
index f8f40a1a685698c07506b2c2f6e5f615c223e8f1..c6e9f34c88363b34be6853d5930b771d1bce5198 100644 (file)
@@ -1,10 +1,9 @@
 package org.argeo.api.cms.directory;
 
-import org.osgi.service.useradmin.User;
-
 /**
  * An entity with credentials which can log in to a system. Can be a real person
  * or not.
  */
-public interface CmsUser extends User {
+public interface CmsUser extends CmsRole {
+       String getDisplayName();
 }
index 7693f6710ae840be75e8eb85677d83f17b00c31e..5af777975769c9f6d10b45eaa7bf6ea72e8a9759 100644 (file)
@@ -8,9 +8,6 @@ import java.util.Set;
 import javax.security.auth.Subject;
 import javax.xml.namespace.QName;
 
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
 /**
  * Provide method interfaces to manage user concepts without accessing directly
  * the userAdmin.
@@ -21,11 +18,11 @@ public interface CmsUserManager {
        Set<UserDirectory> getUserDirectories();
 
        // CurrentUser
-       /** Returns the e-mail of the current logged in user */
-       String getMyMail();
+//     /** Returns the e-mail of the current logged in user */
+//     String getMyMail();
 
        // Other users
-       /** Returns a {@link User} given a username */
+       /** Returns a {@link CmsUser} given a username */
        CmsUser getUser(String username);
 
        /** Can be a group or a user */
@@ -42,7 +39,7 @@ public interface CmsUserManager {
 
        // Search
        /** Returns a filtered list of roles */
-       Role[] getRoles(String filter);
+       CmsRole[] getRoles(String filter);
 
        /** Recursively lists users in a given group. */
        Set<CmsUser> listUsersInGroup(String groupDn, String filter);
@@ -73,18 +70,18 @@ public interface CmsUserManager {
        CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole);
 
        /** Add additional object classes to this role. */
-       void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties);
+       void addObjectClasses(CmsRole role, Set<String> objectClasses, Map<String, Object> additionalProperties);
 
        /** Add additional object classes to this hierarchy unit. */
        void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
                        Map<String, Object> additionalProperties);
 
        /** Add a member to this group. */
-       void addMember(CmsGroup group, Role role);
+       void addMember(CmsGroup group, CmsRole role);
 
        /** Remove a member from this group. */
-       void removeMember(CmsGroup group, Role role);
-       
+       void removeMember(CmsGroup group, CmsRole role);
+
        void edit(Runnable action);
 
        /* MISCELLANEOUS */
@@ -95,7 +92,7 @@ public interface CmsUserManager {
        String getDefaultDomainName();
 
        /**
-        * Search for a {@link User} (might also be a group) whose uid or cn is equals
+        * Search for a {@link CmsUser} (might also be a group) whose uid or cn is equals
         * to localId within the various user repositories defined in the current
         * context.
         */
@@ -118,7 +115,7 @@ public interface CmsUserManager {
 
        void expireAuthTokens(Subject subject);
 
-       UserDirectory getDirectory(Role role);
+       UserDirectory getDirectory(CmsRole role);
 
        /** Create a new hierarchy unit. Does nothing if it already exists. */
        HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path);
index 1f0ecdf75ca2b2abf14f29cd257156caa2ef1cc9..3058e58007fabc63fd8dc75ccb20f10c4715dbc7 100644 (file)
@@ -1,17 +1,15 @@
 package org.argeo.api.cms.directory;
 
-import org.osgi.service.useradmin.Role;
-
 /** Information about a user directory. */
 public interface UserDirectory extends CmsDirectory {
 
-       HierarchyUnit getHierarchyUnit(Role role);
+       HierarchyUnit getHierarchyUnit(CmsRole role);
 
-       Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep);
+       Iterable<? extends CmsRole> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep);
 
-       String getRolePath(Role role);
+       String getRolePath(CmsRole role);
 
-       String getRoleSimpleName(Role role);
+       String getRoleSimpleName(CmsRole role);
 
-       Role getRoleByPath(String path);
+       CmsRole getRoleByPath(String path);
 }
index 4f2cf3765dcb69d6d912a7491511bee1350742f5..4486a9f8b4b907ccb347fa0f78c71b7e3ff30f7a 100644 (file)
@@ -110,30 +110,6 @@ public abstract class AbstractUuidFactory implements UuidFactory {
                return UuidBinaryUtils.fromBytes(arr);
        }
 
-       /*
-        * SPI UTILITIES
-        */
-       /** Guarantees that a byte array of length 6 will be returned. */
-       protected static byte[] toNodeIdBytes(byte[] source, int offset) {
-               if (source == null)
-                       return null;
-               if (offset < 0 || offset + 6 > source.length)
-                       throw new ArrayIndexOutOfBoundsException(offset);
-               byte[] nodeId = new byte[6];
-               System.arraycopy(source, offset, nodeId, 0, 6);
-               return nodeId;
-       }
-
-       /**
-        * Force this node id to be identified as no MAC address.
-        * 
-        * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
-        */
-       protected static void forceToNoMacAddress(byte[] nodeId, int offset) {
-               assert nodeId != null && offset < nodeId.length;
-               nodeId[offset] = (byte) (nodeId[offset] | 1);
-       }
-
        /*
         * DIGEST UTILITIES
         */
index 130a90a84bfb49be449539bd939fc2cc696bd742..d78be0c5d548a470c0d16b06e67095fafaf194cc 100644 (file)
@@ -28,7 +28,7 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements T
                Objects.requireNonNull(nodeId);
                if (offset + 6 > nodeId.length)
                        throw new IllegalArgumentException("Offset too big: " + offset);
-               byte[] defaultNodeId = toNodeIdBytes(nodeId, offset);
+               byte[] defaultNodeId = NodeIdSupplier.toNodeIdBytes(nodeId, offset);
                long nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
                setNodeIdSupplier(() -> nodeIdBase, initialClockRange);
        }
index 31fe3783112661e23f28074d31669870c0db0d42..6cbe98178123bb59b7a4342a87efdd7a260869f9 100644 (file)
@@ -35,24 +35,26 @@ public class MacAddressUuidFactory extends ConcurrentUuidFactory {
                try {
                        localHost = InetAddress.getLocalHost();
                        NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
-                       if (nic != null)
+                       if (nic != null && nic.getHardwareAddress() != null)
                                return hardwareAddressToNodeId(nic);
                        Enumeration<NetworkInterface> netInterfaces = null;
-                       try {
-                               netInterfaces = NetworkInterface.getNetworkInterfaces();
-                       } catch (SocketException e) {
-                               throw new IllegalStateException(e);
-                       }
+                       netInterfaces = NetworkInterface.getNetworkInterfaces();
                        if (netInterfaces == null || !netInterfaces.hasMoreElements())
                                throw new IllegalStateException("No interfaces");
-                       return hardwareAddressToNodeId(netInterfaces.nextElement());
+                       while (netInterfaces.hasMoreElements()) {
+                               // TODO find out public/physical interfaces
+                               nic = netInterfaces.nextElement();
+                               if (nic.getHardwareAddress() != null)
+                                       return hardwareAddressToNodeId(nic);
+                       }
+                       throw new IllegalStateException("No interfaces with a MAC address");
                } catch (UnknownHostException | SocketException e) {
                        throw new IllegalStateException(e);
                }
 
        }
 
-       public static byte[] hardwareAddressToNodeId(NetworkInterface nic) {
+       public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws IllegalStateException {
                try {
                        byte[] hardwareAddress = nic.getHardwareAddress();
                        final int length = 6;
index 81d368d2c8c651c37ea9ab48f7f048db98351f16..1a3cd5673e17d1504277c7c1428de4795ed30490 100644 (file)
@@ -25,4 +25,28 @@ public interface NodeIdSupplier extends Supplier<Long> {
                random.nextBytes(nodeId);
                return nodeId;
        }
+
+       /**
+        * Force this node id to be identified as no MAC address.
+        * 
+        * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
+        */
+       static void forceToNoMacAddress(byte[] nodeId, int offset) {
+               assert nodeId != null && offset < nodeId.length;
+               nodeId[offset] = (byte) (nodeId[offset] | 1);
+       }
+
+       /*
+        * SPI UTILITIES
+        */
+       /** Guarantees that a byte array of length 6 will be returned. */
+       static byte[] toNodeIdBytes(byte[] source, int offset) {
+               if (source == null)
+                       return null;
+               if (offset < 0 || offset + 6 > source.length)
+                       throw new ArrayIndexOutOfBoundsException(offset);
+               byte[] nodeId = new byte[6];
+               System.arraycopy(source, offset, nodeId, 0, 6);
+               return nodeId;
+       }
 }
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/APM.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/APM.java
new file mode 100644 (file)
index 0000000..c91668d
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.uuid.libuuid;
+
+import java.io.Serializable;
+
+/** Package metadata for this package. */
+class APM implements Serializable {
+       /** Major version (equality means backward compatibility). */
+       static final int MAJOR = 2;
+       /** Minor version (if even, equality means forward compatibility). */
+       static final int MINOR = 3;
+       /** serialVersionUID to use for {@link Serializable} classes in this package. */
+       static final long SERIAL = (long) MAJOR << 32 | MINOR & 0xFFFFFFFFL;
+       /** Metadata version. */
+       private static final long serialVersionUID = 2L;
+}
index df3bb317427a3244acfa150c306100f4369d292a..5bc3fe22b01a4d1bd1a8af8d3e01985060035a0c 100644 (file)
@@ -11,12 +11,12 @@ import org.argeo.api.uuid.UuidFactory;
  *             concept that using shared memory in order to limit the JNI
  *             overhead does not yield any significant performance gain. But it
  *             could be an approach for computing and transferring bulk UUIDs
- *             computations in one go, vi
+ *             computations in one go, via
  *             {@link ByteBuffer#allocateDirect(int)}.
  */
 public class DirectLibuuidFactory implements UuidFactory {
        static {
-               System.loadLibrary("Java_org_argeo_api_uuid_libuuid");
+               System.loadLibrary("Java_org_argeo_api_uuid_libuuid." + APM.MAJOR + "." + APM.MINOR);
        }
 
        @Override
index dd54c8159cddf304581df7ee04328ebdc8a000df..ea10de199efad3912f722d9bb91129a31f672bbe 100644 (file)
@@ -8,7 +8,7 @@ import org.argeo.api.uuid.UuidFactory;
 /** An {@link UuidFactory} based on a native library. */
 public class LibuuidFactory implements UuidFactory, TypedUuidFactory {
        static {
-               System.loadLibrary("Java_org_argeo_api_uuid_libuuid");
+               System.loadLibrary("Java_org_argeo_api_uuid_libuuid." + APM.MAJOR + "." + APM.MINOR);
        }
 
        @Override
index 0b9fb57a88ba2a7c767b74c4449a3131d2ff2634..2cd18eeaa18bd5a0aab3fc31105392c447835590 100644 (file)
@@ -5,7 +5,6 @@ Main-Class: org.argeo.cms.cli.ArgeoCli
 
 Class-Path: \
 org.argeo.api.acr.2.1.jar \
-org.argeo.api.cli.2.1.jar \
 org.argeo.api.cms.2.1.jar \
 org.argeo.api.register.2.1.jar \
 org.argeo.api.uuid.2.1.jar \
@@ -107,7 +106,7 @@ org.argeo.init.2.1.jar \
 ../crypto/fips/org.argeo.tp.crypto/bcpkix-fips.1.0.src.jar \
 ../crypto/fips/org.argeo.tp.crypto/bctls-fips.1.0.jar \
 ../crypto/fips/org.argeo.tp.crypto/bctls-fips.1.0.src.jar \
-../osgi/equinox/org.argeo.cms/org.argeo.cms.lib.equinox.2.1.jar \
+../osgi/equinox/org.argeo.cms/org.argeo.cms.lib.equinox.2.3.jar \
 ../osgi/equinox/org.argeo.tp.osgi/org.apache.felix.gogo.command.1.1.jar \
 ../osgi/equinox/org.argeo.tp.osgi/org.apache.felix.gogo.runtime.1.1.jar \
 ../osgi/equinox/org.argeo.tp.osgi/org.apache.felix.gogo.shell.1.1.jar \
index f09995c002a580a5684daf890e871e04148b319d..adff33d30d7c7b9482841a0e336d4c080ed9c043 100644 (file)
@@ -3,9 +3,11 @@ org.osgi.service.http;version=0.0.0,\
 org.osgi.service.http.whiteboard;version=0.0.0,\
 org.osgi.framework.namespace;version=0.0.0,\
 org.argeo.cms.osgi,\
-javax.servlet.*;version="[3,5)",\
 *
 
+# javax.servlet.*;version="[3,5)",\
+
+
 Service-Component:\
 OSGI-INF/pkgServletContext.xml,\
 OSGI-INF/pkgServlet.xml,\
index 9ebf97ed07be957c25203e409a9272986a9badf5..67515e69cbf8c1f0de8c19014e6b5e87b5cf75e8 100644 (file)
@@ -212,7 +212,7 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
 
        static Path bundleToPath(Path frameworkLocation, Bundle bundle) throws IOException {
                String location = bundle.getLocation();
-               if (location.startsWith("initial@reference:file:")) {
+               if (location.startsWith("initial@reference:file:")) {// Eclipse IDE environment
                        location = location.substring("initial@reference:file:".length());
                        Path p = frameworkLocation.getParent().resolve(location).toAbsolutePath();
                        if (Files.exists(p)) {
@@ -225,6 +225,15 @@ public class OsgiExecutionControlProvider implements ExecutionControlProvider {
                                log.warn("Ignore bundle " + p + " as it does not exist");
                                return null;
                        }
+               } else if (location.startsWith("reference:file:")) {// a2+reference
+                       location = location.substring("reference:".length());
+                       Path p = Paths.get(URI.create(location));
+                       if (Files.exists(p)) {
+                               return p;
+                       } else {
+                               log.warn("Ignore bundle " + p + " as it does not exist");
+                               return null;
+                       }
                }
                Path p = Paths.get(location);
                return p;
index 74f0aaed3d8ddeb3154585386f15a5968d8c54eb..4e91ea41c734a51f739a1d82d2d030000788fb38 100644 (file)
@@ -230,6 +230,7 @@ public class JettyHttpServer extends HttpsServer {
                        server.stop();
                        // TODO delete temp dir
                        started = false;
+                       log.debug(() -> "Stopped Jetty server");
                } catch (Exception e) {
                        log.error("Cannot stop Jetty HTTP server", e);
                }
@@ -279,7 +280,7 @@ public class JettyHttpServer extends HttpsServer {
                if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
                        // TODO stop handler first?
                        // FIXME understand compatibility with Jetty 12
-                       //contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+                       // contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
                } else {
                        // FIXME apparently servlets cannot be removed in Jetty, we should replace the
                        // handler
@@ -315,9 +316,16 @@ public class JettyHttpServer extends HttpsServer {
        }
 
        private String httpPortsMsg() {
+               String hostStr = getHost();
+               hostStr = hostStr == null ? "*:" : hostStr + ":";
+               return (httpConnector != null ? "# HTTP " + hostStr + getHttpPort() + " " : "")
+                               + (httpsConnector != null ? "# HTTPS " + hostStr + getHttpsPort() : "");
+       }
 
-               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "")
-                               + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
+       public String getHost() {
+               if (httpConnector == null)
+                       return null;
+               return httpConnector.getHost();
        }
 
        public Integer getHttpPort() {
index 794ffa1abdc4e2d3ade1b15278e542bf64673552..d0569fbdf90111cd331c229d96f5c5bacb0e4862 100644 (file)
@@ -3,6 +3,4 @@ bin.includes = META-INF/,\
                .,\
                OSGI-INF/
 source.. = src/
-additional.bundles = org.argeo.tp.syslogger,\
-                     org.apache.tomcat.jni
-                     
\ No newline at end of file
+additional.bundles = org.argeo.tp.syslogger                     
\ No newline at end of file
index c91ab48058f06944489e559a519b6b0789eb348a..8d9e42e2903066ae2dfb43c95c4368238ada331b 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.cms.ssh;
 
 import java.io.Console;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Arrays;
@@ -51,16 +52,20 @@ public abstract class AbstractSsh {
        }
 
        public void authenticate() {
+               authenticate(System.in);
+       }
+
+       public void authenticate(InputStream in) {
                if (sshKeyPair != null) {
                        session.addPublicKeyIdentity(sshKeyPair.asKeyPair());
                } else {
 
-                       if (!passwordSet) {
+                       if (!passwordSet && in != null) {
                                String password;
                                Console console = System.console();
                                if (console == null) {// IDE
                                        System.out.print("Password: ");
-                                       try (Scanner s = new Scanner(System.in)) {
+                                       try (Scanner s = new Scanner(in)) {
                                                password = s.next();
                                        }
                                } else {
index 98bb045441b51a306988f95346127998ba08e50c..83e7392bca18baff4a50ee0329164fcd0c35ba41 100644 (file)
@@ -172,7 +172,7 @@ public class CmsSshServer implements CmsSshd {
                        // start
                        sshd.start();
 
-                       log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
+                       log.info("# SSH " + (host != null ? host + ":" : "*:") + port);
                } catch (IOException e) {
                        throw new RuntimeException("Cannot start SSH server on port " + port, e);
                }
@@ -207,9 +207,9 @@ public class CmsSshServer implements CmsSshd {
                } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
                                | CertificateException | IllegalArgumentException | UnrecoverableKeyException e) {
                        if (log.isTraceEnabled())
-                               log.error("Cannot add node public key to SSH authorized keys", e);
+                               log.warn("Cannot add node public key to SSH authorized keys", e);
                        else
-                               log.error("Cannot add node public key to SSH authorized keys: " + e.getMessage());
+                               log.warn("Cannot add node public key to SSH authorized keys: " + e);
                        return null;
                }
 
index 7278c285794f1cfac061f77a4840ba03e7a44dfd..870211f762bac951fa2113621558a892a4851ace 100644 (file)
@@ -5,7 +5,7 @@ import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.api.cms.ux.CmsEditable;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.ux.AbstractCmsEditable;
 
 /** {@link CmsEditable} semantics for a {@link Content}. */
@@ -22,7 +22,7 @@ public class ContentCmsEditable extends AbstractCmsEditable {
                canEdit = providedContent.canEdit();
                session = providedContent.getSession();
                provider = providedContent.getProvider();
-               relativePath = ContentUtils.relativize(provider.getMountPath(), content.getPath());
+               relativePath = CmsContent.relativize(provider.getMountPath(), content.getPath());
        }
 
        @Override
diff --git a/org.argeo.cms/OSGI-INF/cmsState.xml b/org.argeo.cms/OSGI-INF/cmsState.xml
deleted file mode 100644 (file)
index 71dc6d4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="false" name="CMS State">
-   <implementation class="org.argeo.cms.internal.runtime.CmsStateImpl"/>
-   <service>
-      <provide interface="org.argeo.api.cms.CmsState"/>
-   </service>
-   <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsUuidFactory.xml b/org.argeo.cms/OSGI-INF/cmsUuidFactory.xml
new file mode 100644 (file)
index 0000000..d55970a
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="UUID Factory">
+   <implementation class="org.argeo.api.uuid.ConcurrentUuidFactory"/>
+   <service>
+      <provide interface="org.argeo.api.uuid.UuidFactory"/>
+   </service>
+   <reference bind="setNodeIdSupplier" cardinality="1..1" interface="org.argeo.api.uuid.NodeIdSupplier" name="NodeIdSupplier" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/uuidFactory.xml b/org.argeo.cms/OSGI-INF/uuidFactory.xml
deleted file mode 100644 (file)
index c1ad6f8..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="UUID Factory">
-   <implementation class="org.argeo.cms.acr.CmsUuidFactory"/>
-   <service>
-      <provide interface="org.argeo.api.uuid.UuidFactory"/>
-   </service>
-</scr:component>
index 01443b5e8150215f77435c4f4a8ce56850f3895e..bcba7939e166a3d7a0bfe2cb55fb33c84b93adf7 100644 (file)
@@ -1,14 +1,16 @@
 Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
 Import-Package: \
-org.osgi.*;version=0.0.0,\
+*
+
+Export-Package:\
+org.argeo.cms.acr.schemas,\
 *
 
 Service-Component:\
 OSGI-INF/cmsOsgiLogger.xml,\
-OSGI-INF/uuidFactory.xml,\
+OSGI-INF/cmsUuidFactory.xml,\
 OSGI-INF/cmsEventBus.xml,\
-OSGI-INF/cmsState.xml,\
 OSGI-INF/transactionManager.xml,\
 OSGI-INF/cmsUserAdmin.xml,\
 OSGI-INF/cmsUserManager.xml,\
diff --git a/org.argeo.cms/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.cms/src/org/argeo/api/cli/CommandArgsException.java
new file mode 100644 (file)
index 0000000..935247f
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.api.cli;
+
+/** Exception thrown when the provided arguments are not correct. */
+public class CommandArgsException extends IllegalArgumentException {
+       private static final long serialVersionUID = -7271050747105253935L;
+       private String commandName;
+       private volatile CommandsCli commandsCli;
+
+       public CommandArgsException(Exception cause) {
+               super(cause.getMessage(), cause);
+       }
+
+       public CommandArgsException(String message) {
+               super(message);
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public void setCommandName(String commandName) {
+               this.commandName = commandName;
+       }
+
+       public CommandsCli getCommandsCli() {
+               return commandsCli;
+       }
+
+       public void setCommandsCli(CommandsCli commandsCli) {
+               this.commandsCli = commandsCli;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/CommandRuntimeException.java b/org.argeo.cms/src/org/argeo/api/cli/CommandRuntimeException.java
new file mode 100644 (file)
index 0000000..52c0334
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.api.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+       private static final long serialVersionUID = 5595999301269377128L;
+
+       private final DescribedCommand<?> command;
+       private final List<String> arguments;
+
+       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               this(null, e, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+               this(message, null, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+               this.command = command;
+               this.arguments = arguments;
+       }
+
+       public DescribedCommand<?> getCommand() {
+               return command;
+       }
+
+       public List<String> getArguments() {
+               return arguments;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/CommandsCli.java b/org.argeo.cms/src/org/argeo/api/cli/CommandsCli.java
new file mode 100644 (file)
index 0000000..5bbfcfa
--- /dev/null
@@ -0,0 +1,157 @@
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** Base class for a CLI managing sub commands. */
+public abstract class CommandsCli implements DescribedCommand<Object> {
+       private final String commandName;
+       private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
+
+       protected final Options options = new Options();
+
+       public CommandsCli(String commandName) {
+               this.commandName = commandName;
+       }
+
+       @Override
+       public Object apply(List<String> args) {
+               String cmd = null;
+               List<String> newArgs = new ArrayList<>();
+               boolean isHelpOption = false;
+               try {
+                       CommandLineParser clParser = new DefaultParser();
+                       CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
+                       List<String> leftOvers = commonCl.getArgList();
+                       for (String arg : leftOvers) {
+                               if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) {
+                                       isHelpOption = true;
+                                       // TODO break?
+                               }
+
+                               if (!arg.startsWith("-") && cmd == null) {
+                                       cmd = arg;
+                               } else {
+                                       newArgs.add(arg);
+                               }
+                       }
+               } catch (ParseException e) {
+                       CommandArgsException cae = new CommandArgsException(e);
+                       throw cae;
+               }
+
+               Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
+
+               // --help option
+               if (!(function instanceof CommandsCli))
+                       if (function instanceof DescribedCommand<?> command)
+                               if (isHelpOption) {
+                                       throw new PrintHelpRequestException(cmd, this);
+//                                     StringWriter out = new StringWriter();
+//                                     HelpCommand.printHelp(command, out);
+//                                     System.out.println(out.toString());
+//                                     return null;
+                               }
+
+               if (function == null)
+                       throw new IllegalArgumentException("Uknown command " + cmd);
+               try {
+                       Object value = function.apply(newArgs);
+                       return value != null ? value.toString() : null;
+               } catch (CommandArgsException e) {
+                       if (e.getCommandName() == null) {
+                               e.setCommandName(cmd);
+                               e.setCommandsCli(this);
+                       }
+                       throw e;
+               } catch (IllegalArgumentException e) {
+                       CommandArgsException cae = new CommandArgsException(e);
+                       cae.setCommandName(cmd);
+                       throw cae;
+               }
+       }
+
+       @Override
+       public Options getOptions() {
+               return options;
+       }
+
+       protected void addCommand(String cmd, Function<List<String>, ?> function) {
+               commands.put(cmd, function);
+
+       }
+
+       @Override
+       public String getUsage() {
+               return "[command]";
+       }
+
+       protected void addCommandsCli(CommandsCli commandsCli) {
+               addCommand(commandsCli.getCommandName(), commandsCli);
+               commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli));
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public Set<String> getSubCommands() {
+               return commands.keySet();
+       }
+
+       public Function<List<String>, ?> getCommand(String command) {
+               return commands.get(command);
+       }
+
+       public HelpCommand getHelpCommand() {
+               return (HelpCommand) getCommand(HelpCommand.HELP);
+       }
+
+       public Function<List<String>, String> getDefaultCommand() {
+               return getHelpCommand();
+       }
+
+       /** In order to implement quickly a main method. */
+       public static void mainImpl(CommandsCli cli, String[] args) {
+               try {
+                       cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli));
+                       Object output = cli.apply(Arrays.asList(args));
+                       if (output != null)
+                               System.out.println(output);
+                       System.exit(0);
+               } catch (PrintHelpRequestException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+                       System.out.println(out.toString());
+               } catch (CommandArgsException e) {
+                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
+                       Throwable cause = e.getCause();
+                       if (!(cause instanceof MissingOptionException))
+                               e.printStackTrace();
+                       if (e.getCommandName() != null) {
+                               StringWriter out = new StringWriter();
+                               HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+                               System.err.println(out.toString());
+                       } else {
+                               e.printStackTrace();
+                       }
+                       System.exit(1);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/DescribedCommand.java b/org.argeo.cms/src/org/argeo/api/cli/DescribedCommand.java
new file mode 100644 (file)
index 0000000..51cb2ce
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** A command that can be described. */
+public interface DescribedCommand<T> extends Function<List<String>, T> {
+       default Options getOptions() {
+               return new Options();
+       }
+
+       String getDescription();
+
+       default String getUsage() {
+               return null;
+       }
+
+       default String getExamples() {
+               return null;
+       }
+
+       default CommandLine toCommandLine(List<String> args) {
+               try {
+                       DefaultParser parser = new DefaultParser();
+                       return parser.parse(getOptions(), args.toArray(new String[args.size()]));
+               } catch (ParseException e) {
+                       throw new CommandArgsException(e);
+               }
+       }
+
+       /** In order to implement quickly a main method. */
+       public static void mainImpl(DescribedCommand<?> command, String[] args) {
+               try {
+                       Object output = command.apply(Arrays.asList(args));
+                       System.out.println(output);
+                       System.exit(0);
+               } catch (PrintHelpRequestException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(command, out);
+                       System.out.println(out.toString());
+                       System.exit(1);
+               } catch (IllegalArgumentException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(command, out);
+                       System.err.println(out.toString());
+                       System.exit(1);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/HelpCommand.java b/org.argeo.cms/src/org/argeo/api/cli/HelpCommand.java
new file mode 100644 (file)
index 0000000..cd51d76
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.api.cli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** A special command that can describe {@link DescribedCommand}. */
+public class HelpCommand implements DescribedCommand<String> {
+       /**
+        * System property forcing the root command to this value (typically the name of
+        * a script).
+        */
+       public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand";
+
+       final static String HELP = "help";
+       final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build();
+
+       private CommandsCli commandsCli;
+       private CommandsCli parentCommandsCli;
+
+       // Help formatting
+       private static int helpWidth = 80;
+       private static int helpLeftPad = 4;
+       private static int helpDescPad = 20;
+
+       public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
+               super();
+               this.parentCommandsCli = parentCommandsCli;
+               this.commandsCli = commandsCli;
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               StringWriter out = new StringWriter();
+
+               if (args.size() == 0) {// overview
+                       printHelp(commandsCli, out);
+               } else {
+                       String cmd = args.get(0);
+                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+                       if (function == null)
+                               return "Command " + cmd + " not found.";
+                       Options options;
+                       String examples;
+                       DescribedCommand<?> command = null;
+                       if (function instanceof DescribedCommand) {
+                               command = (DescribedCommand<?>) function;
+                               options = command.getOptions();
+                               examples = command.getExamples();
+                       } else {
+                               options = new Options();
+                               examples = null;
+                       }
+                       String description = getShortDescription(function);
+                       String commandCall = getCommandUsage(cmd, command);
+                       HelpFormatter formatter = new HelpFormatter();
+                       formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
+                                       helpDescPad, examples, false);
+               }
+               return out.toString();
+       }
+
+       private static String getShortDescription(Function<List<String>, ?> function) {
+               if (function instanceof DescribedCommand) {
+                       return ((DescribedCommand<?>) function).getDescription();
+               } else {
+                       return function.toString();
+               }
+       }
+
+       public String getCommandUsage(String cmd, DescribedCommand<?> command) {
+               String commandCall = getCommandCall(commandsCli) + " " + cmd;
+               assert command != null;
+               if (command != null && command.getUsage() != null) {
+                       commandCall = commandCall + " " + command.getUsage();
+               }
+               return commandCall;
+       }
+
+       @Override
+       public String getDescription() {
+               return "Shows this help or describes a command";
+       }
+
+       @Override
+       public String getUsage() {
+               return "[command]";
+       }
+
+       public CommandsCli getParentCommandsCli() {
+               return parentCommandsCli;
+       }
+
+       protected String getCommandCall(CommandsCli commandsCli) {
+               HelpCommand hc = commandsCli.getHelpCommand();
+               if (hc.getParentCommandsCli() != null) {
+                       return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
+               } else {
+                       String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY);
+                       if (rootCommand != null)
+                               return rootCommand;
+                       return commandsCli.getCommandName();
+               }
+       }
+
+       public static void printHelp(DescribedCommand<?> command, StringWriter out) {
+               String usage = "java " + command.getClass().getName()
+                               + (command.getUsage() != null ? " " + command.getUsage() : "");
+               HelpFormatter formatter = new HelpFormatter();
+               Options options = command.getOptions();
+               options.addOption(HelpCommand.HELP_OPTION);
+               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+                               helpDescPad, command.getExamples(), false);
+
+       }
+
+       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
+               if (commandName == null) {
+                       printHelp(commandsCli, out);
+                       return;
+               }
+               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
+               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
+               HelpFormatter formatter = new HelpFormatter();
+               Options options = command.getOptions();
+               options.addOption(HelpCommand.HELP_OPTION);
+               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+                               helpDescPad, command.getExamples(), false);
+
+       }
+
+       public static void printHelp(CommandsCli commandsCli, StringWriter out) {
+               out.append(commandsCli.getDescription()).append('\n');
+               String leftPad = spaces(helpLeftPad);
+               for (String cmd : commandsCli.getSubCommands()) {
+                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+                       assert function != null;
+                       out.append(leftPad);
+                       out.append(cmd);
+                       // TODO deal with long commands
+                       out.append(spaces(helpDescPad - cmd.length()));
+                       out.append(getShortDescription(function));
+                       out.append('\n');
+               }
+       }
+
+       private static String spaces(int count) {
+               // Java 11
+               // return " ".repeat(count);
+               if (count <= 0)
+                       return "";
+               else {
+                       StringBuilder sb = new StringBuilder(count);
+                       for (int i = 0; i < count; i++)
+                               sb.append(' ');
+                       return sb.toString();
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/PrintHelpRequestException.java b/org.argeo.cms/src/org/argeo/api/cli/PrintHelpRequestException.java
new file mode 100644 (file)
index 0000000..017386b
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.api.cli;
+
+/** An exception indicating that help should be printed. */
+class PrintHelpRequestException extends RuntimeException {
+
+       private static final long serialVersionUID = -9029122270660656639L;
+
+       private String commandName;
+       private volatile CommandsCli commandsCli;
+
+       public PrintHelpRequestException(String commandName, CommandsCli commandsCli) {
+               super();
+               this.commandName = commandName;
+               this.commandsCli = commandsCli;
+       }
+
+       public static long getSerialversionuid() {
+               return serialVersionUID;
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public CommandsCli getCommandsCli() {
+               return commandsCli;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/package-info.java b/org.argeo.cms/src/org/argeo/api/cli/package-info.java
new file mode 100644 (file)
index 0000000..114fd02
--- /dev/null
@@ -0,0 +1,2 @@
+/** Command line API. */
+package org.argeo.api.cli;
\ No newline at end of file
index ad12a8665840565a75eb619185d08cb82c40ed6f..c879a4d2d7f262145b3c216d486a67959ad8a2c7 100644 (file)
@@ -17,8 +17,10 @@ import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
+import org.argeo.api.cms.auth.ImpliedByPrincipal;
+import org.argeo.api.cms.auth.RoleNameUtils;
+import org.argeo.api.cms.auth.SystemRole;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.util.CurrentSubject;
 import org.osgi.service.useradmin.Authorization;
diff --git a/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java b/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java
deleted file mode 100644 (file)
index 04302c4..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.cms;
-
-import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.ArgeoNamespace;
-import org.argeo.api.acr.NamespaceUtils;
-import org.argeo.cms.directory.ldap.LdapNameUtils;
-
-/** Simplifies analysis of system roles. */
-public class RoleNameUtils {
-       public static String getLastRdnValue(String dn) {
-               return LdapNameUtils.getLastRdnValue(dn);
-//             // we don't use LdapName for portability with Android
-//             // TODO make it more robust
-//             String[] parts = dn.split(",");
-//             String[] rdn = parts[0].split("=");
-//             return rdn[1];
-       }
-
-       public static QName getLastRdnAsName(String dn) {
-               String cn = getLastRdnValue(dn);
-               QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn);
-               return roleName;
-       }
-
-       public static boolean isSystemRole(QName roleName) {
-               return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI);
-       }
-
-       public static String getParent(String dn) {
-               int index = dn.indexOf(',');
-               return dn.substring(index + 1);
-       }
-
-       /** Up two levels. */
-       public static String getContext(String dn) {
-               return getParent(getParent(dn));
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/SystemRole.java b/org.argeo.cms/src/org/argeo/cms/SystemRole.java
deleted file mode 100644 (file)
index 9564399..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.cms;
-
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-
-/** A programmatic role. */
-public interface SystemRole {
-       QName qName();
-
-       /** Whether this role is implied for this authenticated user. */
-       default boolean implied(Subject subject, String context) {
-               return implied(qName(), subject, context);
-       }
-
-       /** Whether this role is implied for this distinguished name. */
-       default boolean implied(String dn, String context) {
-               String roleContext = RoleNameUtils.getContext(dn);
-               QName roleName = RoleNameUtils.getLastRdnAsName(dn);
-               return roleContext.equalsIgnoreCase(context) && qName().equals(roleName);
-       }
-
-       /**
-        * Whether this role is implied for this authenticated subject. If context is
-        * <code>null</code>, it is not considered; this should be used to build user
-        * interfaces, but not to authorise.
-        */
-       static boolean implied(QName name, Subject subject, String context) {
-               Set<ImpliedByPrincipal> roles = subject.getPrincipals(ImpliedByPrincipal.class);
-               for (ImpliedByPrincipal role : roles) {
-                       if (role.isSystemRole()) {
-                               if (role.getRoleName().equals(name)) {
-                                       // !! if context is not specified, it is considered irrelevant
-                                       if (context == null)
-                                               return true;
-                                       if (role.getContext().equalsIgnoreCase(context)
-                                                       || role.getContext().equals(CmsConstants.NODE_BASEDN))
-                                               return true;
-                               }
-                       }
-               }
-               return false;
-       }
-}
index 1acdcc3809ba3914a4c256fb814baed41ce3d880..7eb4138ab64c8183a94143e8ff0780b0765540e4 100644 (file)
@@ -21,7 +21,7 @@ import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.util.LangUtils;
 
 /** Partial reference implementation of a {@link ProvidedContent}. */
-public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements CmsContent {
        private final ProvidedSession session;
 
        // cache
@@ -96,7 +96,7 @@ public abstract class AbstractContent extends AbstractMap<QName, Object> impleme
                        if (CrName.root.qName().equals(name))
                                continue ancestors;
 
-                       path.append('/');
+                       path.append(PATH_SEPARATOR);
                        path.append(NamespaceUtils.toPrefixedName(name));
                        int siblingIndex = c.getSiblingIndex();
                        if (siblingIndex != 1)
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContent.java
new file mode 100644 (file)
index 0000000..184b2ce
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.cms.acr;
+
+import java.util.Objects;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+
+/** A content within a CMS system. */
+public interface CmsContent extends ProvidedContent {
+
+       /**
+        * Split a path (with {@link Content#PATH_SEPARATOR} separator) in an array of
+        * length 2, the first part being the parent path (which could be either
+        * absolute or relative), the second one being the last segment, (guaranteed to
+        * be without a '/').
+        */
+       static String[] getParentPath(String path) {
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               if (path.length() == 0)
+                       throw new IllegalArgumentException("Path cannot be empty");
+               ContentUtils.checkDoubleSlash(path);
+               int parentIndex = path.lastIndexOf(PATH_SEPARATOR);
+               if (parentIndex == path.length() - 1) {// trailing '/'
+                       path = path.substring(0, path.length() - 1);
+                       parentIndex = path.lastIndexOf(PATH_SEPARATOR);
+               }
+
+               if (parentIndex == -1) // no '/'
+                       return new String[] { "", path };
+
+               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : ContentUtils.PATH_SEPARATOR_STRING,
+                               path.substring(parentIndex + 1) };
+       }
+
+       /**
+        * Constructs a relative path between a base path and a given path.
+        * 
+        * @throws IllegalArgumentException if the base path is not an ancestor of the
+        *                                  path
+        */
+       static String relativize(String basePath, String path) throws IllegalArgumentException {
+               Objects.requireNonNull(basePath);
+               Objects.requireNonNull(path);
+               if (!path.startsWith(basePath))
+                       throw new IllegalArgumentException(basePath + " is not an ancestor of " + path);
+               String relativePath = path.substring(basePath.length());
+               if (relativePath.length() > 0 && relativePath.charAt(0) == PATH_SEPARATOR)
+                       relativePath = relativePath.substring(1);
+               return relativePath;
+       }
+
+}
index 429b759fc03a4c6e4ff9fe5c20afd6f5bce12caa..02abd4cda2641d4ff2fcb2e3743f1061e787fd9b 100644 (file)
@@ -1,6 +1,9 @@
 package org.argeo.cms.acr;
 
+import static java.lang.System.Logger.Level.ERROR;
+
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.util.Objects;
 
@@ -52,8 +55,18 @@ public enum CmsContentNamespace implements ContentNamespace {
                Objects.requireNonNull(namespace);
                this.namespace = namespace;
                if (resourceFileName != null) {
-                       resource = getClass().getResource(RESOURCE_BASE + resourceFileName);
-                       Objects.requireNonNull(resource);
+                       // resource = getClass().getResource(RESOURCE_BASE + resourceFileName);
+                       try {
+                               // FIXME workaround when in nested OSGi frameworks
+                               resource = URI.create("platform:/plugin/org.argeo.cms" + RESOURCE_BASE + resourceFileName).toURL();
+                       } catch (MalformedURLException e) {
+                               resource = null;
+                               System.getLogger(CmsContentNamespace.class.getName()).log(ERROR,
+                                               "Cannot load " + resourceFileName + ": " + e.getMessage());
+                               // throw new IllegalArgumentException("Cannot convert " + resourceFileName + "
+                               // to URL");
+                       }
+                       // Objects.requireNonNull(resource);
                }
                if (publicUrl != null)
                        try {
index c782256e282acb94b97a7233ce0ddb4ad9d2cf0c..04a6fea6cdf15ad231127f66b79c7153981289d1 100644 (file)
@@ -74,7 +74,7 @@ class CmsContentSession implements ProvidedSession, UuidIdentified {
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = ContentUtils.relativize(mountPath, path);
+               String relativePath = CmsContent.relativize(mountPath, path);
                ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath);
                return content;
        }
@@ -85,7 +85,7 @@ class CmsContentSession implements ProvidedSession, UuidIdentified {
                        throw new IllegalArgumentException(path + " is not an absolute path");
                ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
                String mountPath = contentProvider.getMountPath();
-               String relativePath = ContentUtils.relativize(mountPath, path);
+               String relativePath = CmsContent.relativize(mountPath, path);
                return contentProvider.exists(this, relativePath);
        }
 
@@ -113,7 +113,7 @@ class CmsContentSession implements ProvidedSession, UuidIdentified {
         */
        @Override
        public Content getMountPoint(String path) {
-               String[] parent = ContentUtils.getParentPath(path);
+               String[] parent = CmsContent.getParentPath(path);
                ProvidedContent mountParent = (ProvidedContent) get(parent[0]);
 //                     Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
                return mountParent.getMountPoint(parent[1]);
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java
deleted file mode 100644 (file)
index 357a010..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.argeo.cms.acr;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.BitSet;
-import java.util.Enumeration;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.uuid.ConcurrentUuidFactory;
-import org.argeo.api.uuid.NodeIdSupplier;
-import org.argeo.api.uuid.UuidBinaryUtils;
-
-public class CmsUuidFactory extends ConcurrentUuidFactory {
-       private final static CmsLog log = CmsLog.getLog(CmsUuidFactory.class);
-
-       public CmsUuidFactory(byte[] nodeId) {
-               super(0, nodeId);
-               assert createTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0];
-       }
-
-       public CmsUuidFactory() {
-               this(getIpBytes());
-       }
-
-       /** Returns an SHA1 digest of one of the IP addresses. */
-       protected static byte[] getIpBytes() {
-               Enumeration<NetworkInterface> netInterfaces = null;
-               try {
-                       netInterfaces = NetworkInterface.getNetworkInterfaces();
-               } catch (SocketException e) {
-                       throw new IllegalStateException(e);
-               }
-               if (netInterfaces == null)
-                       throw new IllegalStateException("No interfaces");
-
-               InetAddress selectedIpv6 = null;
-               InetAddress selectedIpv4 = null;
-               netInterfaces: while (netInterfaces.hasMoreElements()) {
-                       NetworkInterface netInterface = netInterfaces.nextElement();
-                       byte[] hardwareAddress = null;
-                       try {
-                               hardwareAddress = netInterface.getHardwareAddress();
-                               if (hardwareAddress != null) {
-                                       // first IPv6
-                                       addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
-                                               InetAddress ip = addr.getAddress();
-                                               if (ip instanceof Inet6Address) {
-                                                       Inet6Address ipv6 = (Inet6Address) ip;
-                                                       if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
-                                                               continue addr;
-                                                       selectedIpv6 = ipv6;
-                                                       break netInterfaces;
-                                               }
-
-                                       }
-                                       // then IPv4
-                                       addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
-                                               InetAddress ip = addr.getAddress();
-                                               if (ip instanceof Inet4Address) {
-                                                       Inet4Address ipv4 = (Inet4Address) ip;
-                                                       if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
-                                                               continue addr;
-                                                       selectedIpv4 = ipv4;
-                                               }
-
-                                       }
-                               }
-                       } catch (SocketException e) {
-                               throw new IllegalStateException(e);
-                       }
-               }
-               InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
-               if (selectedIp == null) {
-                       log.warn("No IP address found, using a random node id for UUID generation");
-                       return NodeIdSupplier.randomNodeId();
-               }
-               byte[] digest = sha1(selectedIp.getAddress());
-               log.info("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id");
-               byte[] nodeId = toNodeIdBytes(digest, 0);
-               // marks that this is not based on MAC address
-               forceToNoMacAddress(nodeId, 0);
-               return nodeId;
-       }
-
-}
index facb5933bf3ebe743ce99eeed8e57c300d0cb11b..5676e5c108b55e9a4a948b10ff1ca20f688d292d 100644 (file)
@@ -1,11 +1,12 @@
 package org.argeo.cms.acr;
 
+import static org.argeo.api.acr.Content.PATH_SEPARATOR;
+
 import java.io.PrintStream;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.StringTokenizer;
 import java.util.function.BiConsumer;
@@ -22,14 +23,18 @@ import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
 import org.argeo.cms.util.CurrentSubject;
-import org.osgi.service.useradmin.Role;
 
 /** Utilities and routines around {@link Content}. */
 public class ContentUtils {
+       // Optimisations
+       static final String PATH_SEPARATOR_STRING = Character.toString(PATH_SEPARATOR);
+       private static final String DOUBLE_PATH_SEPARATOR = PATH_SEPARATOR_STRING + PATH_SEPARATOR_STRING;
+
        public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
                traverse(content, doIt, (Integer) null);
        }
@@ -55,76 +60,53 @@ public class ContentUtils {
                        sb.append("  ");
                }
                String prefix = sb.toString();
-               out.println(prefix + content.getName());
-               for (QName key : content.keySet()) {
-                       out.println(prefix + " " + key + "=" + content.get(key));
-               }
+               String txt = "";
                if (printText) {
                        if (content.hasText()) {
-                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
+                               final int MAX_LENGTH = 64;
+                               txt = content.getText().trim();
+                               if (txt.length() > MAX_LENGTH)
+                                       txt = txt.substring(0, 64) + " ...";
+                               txt = " : " + txt;
                        }
                }
+               out.println(prefix + content.getName() + txt);
+               for (QName key : content.keySet()) {
+                       out.println(prefix + " " + key + "=" + content.get(key));
+               }
        }
 
 //     public static <T> boolean isString(T t) {
 //             return t instanceof String;
 //     }
 
-       public static final char SLASH = '/';
-       public static final String SLASH_STRING = Character.toString(SLASH);
-       public static final String EMPTY = "";
-
-       /**
-        * Split a path (with '/' separator) in an array of length 2, the first part
-        * being the parent path (which could be either absolute or relative), the
-        * second one being the last segment, (guaranteed to be without a '/').
-        */
-       public static String[] getParentPath(String path) {
-               if (path == null)
-                       throw new IllegalArgumentException("Path cannot be null");
-               if (path.length() == 0)
-                       throw new IllegalArgumentException("Path cannot be empty");
-               checkDoubleSlash(path);
-               int parentIndex = path.lastIndexOf(SLASH);
-               if (parentIndex == path.length() - 1) {// trailing '/'
-                       path = path.substring(0, path.length() - 1);
-                       parentIndex = path.lastIndexOf(SLASH);
-               }
-
-               if (parentIndex == -1) // no '/'
-                       return new String[] { EMPTY, path };
-
-               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
-                               path.substring(parentIndex + 1) };
-       }
-
        public static String toPath(List<String> segments) {
                // TODO checks
-               StringJoiner sj = new StringJoiner("/");
+               StringJoiner sj = new StringJoiner(PATH_SEPARATOR_STRING);
                segments.forEach((s) -> sj.add(s));
                return sj.toString();
        }
 
-       public static List<String> toPathSegments(String path) {
+       static List<String> toPathSegments(String path) {
                List<String> res = new ArrayList<>();
-               if (EMPTY.equals(path) || Content.ROOT_PATH.equals(path))
+               if ("".equals(path) || Content.ROOT_PATH.equals(path))
                        return res;
                collectPathSegments(path, res);
                return res;
        }
 
-       private static void collectPathSegments(String path, List<String> segments) {
-               String[] parent = getParentPath(path);
-               if (EMPTY.equals(parent[1])) // root
+       static void collectPathSegments(String path, List<String> segments) {
+               String[] parent = CmsContent.getParentPath(path);
+               if ("".equals(parent[1])) // root
                        return;
                segments.add(0, parent[1]);
-               if (EMPTY.equals(parent[0])) // end
+               if ("".equals(parent[0])) // end
                        return;
                collectPathSegments(parent[0], segments);
        }
 
-       public static void checkDoubleSlash(String path) {
-               if (path.contains(SLASH + "" + SLASH))
+       static void checkDoubleSlash(String path) {
+               if (path.contains(DOUBLE_PATH_SEPARATOR))
                        throw new IllegalArgumentException("Path " + path + " contains //");
        }
 
@@ -142,7 +124,7 @@ public class ContentUtils {
         * DIRECTORY
         */
 
-       public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
+       public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, CmsRole role) {
                UserDirectory userDirectory = userManager.getDirectory(role);
                String path = directoryPath(userDirectory) + userDirectory.getRolePath(role);
                Content content = contentSession.get(path);
@@ -151,7 +133,7 @@ public class ContentUtils {
 
        public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) {
                CmsDirectory directory = hierarchyUnit.getDirectory();
-               StringJoiner relativePath = new StringJoiner(SLASH_STRING);
+               StringJoiner relativePath = new StringJoiner(PATH_SEPARATOR_STRING);
                buildHierarchyUnitPath(hierarchyUnit, relativePath);
                String path = directoryPath(directory) + relativePath.toString();
                Content content = contentSession.get(path);
@@ -160,7 +142,7 @@ public class ContentUtils {
 
        /** The path to this {@link CmsDirectory}. Ends with a /. */
        private static String directoryPath(CmsDirectory directory) {
-               return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH;
+               return CmsContentRepository.DIRECTORY_BASE + PATH_SEPARATOR + directory.getName() + PATH_SEPARATOR;
        }
 
        /** Recursively build a relative path of a {@link HierarchyUnit}. */
@@ -184,7 +166,7 @@ public class ContentUtils {
                                return content;
                        }
                } else {
-                       String[] parentPath = getParentPath(path);
+                       String[] parentPath = CmsContent.getParentPath(path);
                        Content parent = createCollections(session, parentPath[0]);
                        Content content = parent.add(parentPath[1], DName.collection.qName());
                        return content;
@@ -214,23 +196,6 @@ public class ContentUtils {
                return CurrentSubject.callAs(cmsSession.getSubject(), () -> contentRepository.get());
        }
 
-       /**
-        * Constructs a relative path between a base path and a given path.
-        * 
-        * @throws IllegalArgumentException if the base path is not an ancestor of the
-        *                                  path
-        */
-       public static String relativize(String basePath, String path) throws IllegalArgumentException {
-               Objects.requireNonNull(basePath);
-               Objects.requireNonNull(path);
-               if (!path.startsWith(basePath))
-                       throw new IllegalArgumentException(basePath + " is not an ancestor of " + path);
-               String relativePath = path.substring(basePath.length());
-               if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
-                       relativePath = relativePath.substring(1);
-               return relativePath;
-       }
-
        /** A path in the node repository */
        public static String getDataPath(Content node) {
                // TODO make it more configurable?
index 90d621b761e57359ac14b02d2aeed2de47526146..39b2038fb02676bbf09677e0b46b34a966d6184d 100644 (file)
@@ -28,7 +28,7 @@ class MountManager {
                partitions.put(mountPath, contentProvider);
                if ("/".equals(mountPath))// root
                        return;
-               String[] parentPath = ContentUtils.getParentPath(mountPath);
+               String[] parentPath = CmsContent.getParentPath(mountPath);
                Content parent = systemSession.get(parentPath[0]);
                Content mount = parent.add(parentPath[1]);
                mount.put(CrName.mount.qName(), "true");
@@ -57,7 +57,7 @@ class MountManager {
                String mountPath = floorEntry.getKey();
                if (!path.startsWith(mountPath)) {
                        // FIXME make it more robust and find when there is no content provider
-                       String[] parent = ContentUtils.getParentPath(path);
+                       String[] parent = CmsContent.getParentPath(path);
                        return findContentProvider(parent[0]);
                        // throw new IllegalArgumentException("Path " + path + " doesn't have a content
                        // provider");
index c3bea5b60fae239c550179641d004f383730c2c6..96de3ac4a5ddb3e3b24b8a9e25e93632104b1d33 100644 (file)
@@ -22,7 +22,6 @@ import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
 
 import org.apache.xerces.impl.xs.XSImplementationImpl;
-import org.apache.xerces.impl.xs.util.StringListImpl;
 import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
 import org.apache.xerces.xs.StringList;
 import org.apache.xerces.xs.XSAttributeDeclaration;
@@ -64,7 +63,7 @@ class TypesManager {
        // cached
        private Schema schema;
        private DocumentBuilderFactory documentBuilderFactory;
-       private XSModel xsModel;
+       // private XSModel xsModel;
        private SortedMap<QName, Map<QName, CrAttributeType>> types;
 
        private boolean validating = false;
@@ -118,6 +117,11 @@ class TypesManager {
                                List<StreamSource> sourcesToUse = new ArrayList<>();
                                for (URL sourceUrl : sources) {
                                        sourcesToUse.add(new StreamSource(sourceUrl.toExternalForm()));
+//                                     try {
+//                                             sourcesToUse.add(new StreamSource(sourceUrl.openStream()));
+//                                     } catch (IOException e) {
+//                                             log.error("Cannot open schema source " + sourceUrl);
+//                                     }
                                }
                                schema = schemaFactory.newSchema(sourcesToUse.toArray(new Source[sourcesToUse.size()]));
 //                             for (StreamSource source : sourcesToUse) {
@@ -153,8 +157,8 @@ class TypesManager {
                                for (URL sourceUrl : sources) {
                                        systemIds.add(sourceUrl.toExternalForm());
                                }
-                               StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
-                               xsModel = xsLoader.loadURIList(sl);
+                               StringList sl = xsImplementation.createStringList(systemIds.toArray(new String[systemIds.size()]));
+                               XSModel xsModel = xsLoader.loadURIList(sl);
 
                                // types
 //                     XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
@@ -163,16 +167,17 @@ class TypesManager {
 //                             QName type = new QName(eDec.getNamespace(), eDec.getName());
 //                             types.add(type);
 //                     }
-                               collectTypes();
-                               
+                               collectTypes(xsModel);
+
                                log.debug("Created XS model");
+                               // printTypes();
                        }
                } catch (XSException | SAXException e) {
                        throw new IllegalStateException("Cannot reload types", e);
                }
        }
 
-       private void collectTypes() {
+       private void collectTypes(XSModel xsModel) {
                types.clear();
                // elements
                XSNamedMap topLevelElements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
@@ -429,7 +434,17 @@ class TypesManager {
                }
        }
 
-       public void printTypes() {
+       void printTypes() {
+               for (QName type : types.keySet()) {
+                       Map<QName, CrAttributeType> attrs = types.get(type);
+                       System.out.println("## " + type);
+                       for (QName attr : attrs.keySet()) {
+                               System.out.println(" " + attr + " : " + attrs.get(attr));
+                       }
+               }
+       }
+
+       void printTypes(XSModel xsModel) {
                if (xsModel != null)
                        try {
 
index 96e6eeaf3836c57887a6acb3c70802d2cf3a1909..b5915406e3411c045722a00a0f1c3b8b2152da2d 100644 (file)
@@ -15,7 +15,7 @@ import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.dav.DavResponse;
 import org.argeo.cms.http.HttpStatus;
 
@@ -41,7 +41,7 @@ public class DavContent extends AbstractContent {
 
        @Override
        public QName getName() {
-               String fileName = ContentUtils.getParentPath(uri.getPath())[1];
+               String fileName = CmsContent.getParentPath(uri.getPath())[1];
                ContentName name = NamespaceUtils.parsePrefixedName(provider, fileName);
                return name;
        }
@@ -49,7 +49,7 @@ public class DavContent extends AbstractContent {
        @Override
        public Content getParent() {
                try {
-                       String parentPath = ContentUtils.getParentPath(uri.getPath())[0];
+                       String parentPath = CmsContent.getParentPath(uri.getPath())[0];
                        URI parentUri = new URI(uri.getScheme(), uri.getHost(), parentPath, null);
                        return provider.getDavContent(getSession(), parentUri);
                } catch (URISyntaxException e) {
index ab84aef37131b4a780fb92644f9ed4a96754161f..fc48dace5ca6ffd850fbf2ae3f40c7b372c35185 100644 (file)
@@ -10,12 +10,12 @@ import org.argeo.api.acr.ContentNotFoundException;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsUser;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
 import org.argeo.cms.acr.AbstractSimpleContentProvider;
 import org.argeo.cms.acr.ContentUtils;
-import org.osgi.service.useradmin.User;
 
 /** A {@link ContentProvider} based on a {@link CmsUserManager} service. */
 public class DirectoryContentProvider extends AbstractSimpleContentProvider<CmsUserManager> {
@@ -62,7 +62,7 @@ public class DirectoryContentProvider extends AbstractSimpleContentProvider<CmsU
 //                     } catch (InvalidNameException e) {
 //                             throw new IllegalStateException("Cannot interpret " + segments + " as DN", e);
 //                     }
-                       User user = (User) userDirectory.getRoleByPath(pathWithinUserDirectory);
+                       CmsUser user = (CmsUser) userDirectory.getRoleByPath(pathWithinUserDirectory);
                        if (user != null) {
                                HierarchyUnit parent = userDirectory.getHierarchyUnit(user);
                                return new RoleContent(session, this, new HierarchyUnitContent(session, this, parent), user);
index 5acf8ab63fc07711f1956ed4fa3380c952f294a5..e4c637f20b8fd2fca0cdccaf0f295e3594d2f8e0 100644 (file)
@@ -10,13 +10,12 @@ import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.DName;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
-import org.osgi.service.useradmin.Role;
 
 class HierarchyUnitContent extends AbstractDirectoryContent {
        private HierarchyUnit hierarchyUnit;
@@ -58,7 +57,7 @@ class HierarchyUnitContent extends AbstractDirectoryContent {
                for (HierarchyUnit hu : hierarchyUnit.getDirectHierarchyUnits(false))
                        lst.add(new HierarchyUnitContent(getSession(), provider, hu));
 
-               for (Role role : ((UserDirectory) hierarchyUnit.getDirectory()).getHierarchyUnitRoles(hierarchyUnit, null,
+               for (CmsRole role : ((UserDirectory) hierarchyUnit.getDirectory()).getHierarchyUnitRoles(hierarchyUnit, null,
                                false))
                        lst.add(new RoleContent(getSession(), provider, this, role));
                return lst.iterator();
index 64feb1d6735955310ee458a00c50a53adbe842c8..c72ea37607c92ff26369aeee211bf0ae83286728 100644 (file)
@@ -7,22 +7,24 @@ import javax.xml.namespace.QName;
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.api.cms.directory.UserDirectory;
 import org.osgi.service.useradmin.Role;
 
 class RoleContent extends AbstractDirectoryContent {
 
        private HierarchyUnitContent parent;
-       private Role role;
+       private CmsRole role;
 
        public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent,
-                       Role role) {
+                       CmsRole role) {
                super(session, provider);
                this.parent = parent;
                this.role = role;
        }
 
        @Override
+       @Deprecated
        Dictionary<String, Object> doGetProperties() {
                return role.getProperties();
        }
@@ -41,8 +43,11 @@ class RoleContent extends AbstractDirectoryContent {
        @SuppressWarnings("unchecked")
        @Override
        public <A> A adapt(Class<A> clss) {
-               if (Role.class.isAssignableFrom(clss))
+               if (CmsRole.class.isAssignableFrom(clss))
                        return (A) role;
+               // TODO do we need this?
+//             if (Role.class.isAssignableFrom(clss))
+//                     return (A) role;
                return super.adapt(clss);
        }
 
index 13b19aabb901a468b4c20ef25df9e3bcf82e3500..de21d32a60da859f41f44396e1c0738dd41ffff2 100644 (file)
@@ -42,7 +42,7 @@ import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.util.FsUtils;
 
 /** Content persisted as a filesystem {@link Path}. */
@@ -332,7 +332,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        String mountPath = provider.getMountPath();
                        if (mountPath == null || mountPath.equals("/"))
                                return null;
-                       String[] parent = ContentUtils.getParentPath(mountPath);
+                       String[] parent = CmsContent.getParentPath(mountPath);
                        return getSession().get(parent[0]);
                }
                return new FsContent(this, path.getParent());
index 0686be7cb59b2cf63d034fc8331a1d1884e39f08..1d751149101caaabad4e17b697b4bb699de94e9e 100644 (file)
@@ -34,7 +34,7 @@ import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.argeo.cms.acr.AbstractContent;
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.w3c.dom.Attr;
 import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
@@ -239,8 +239,8 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                        if (Content.ROOT_PATH.equals(mountPath)) {
                                return null;
                        }
-                       String[] parent = ContentUtils.getParentPath(mountPath);
-                       if (ContentUtils.EMPTY.equals(parent[0]))
+                       String[] parent = CmsContent.getParentPath(mountPath);
+                       if ("".equals(parent[0]))
                                return null;
                        return getSession().get(parent[0]);
                }
index 845a6ab4c22a23629194b6bab0e3a0609d8ed2d9..20f7eb1534cbb79fb21bd17e0fe93585e17bb706 100644 (file)
@@ -75,9 +75,13 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
        protected NodeList findContent(String relativePath) {
                if (relativePath.startsWith("/"))
                        throw new IllegalArgumentException("Relative path cannot start with /");
-               String xPathExpression = '/' + relativePath;
-               if (Content.ROOT_PATH.equals(mountPath)) // repository root
-                       xPathExpression = "/" + CrName.root.get() + xPathExpression;
+               String xPathExpression;
+               if (Content.ROOT_PATH.equals(mountPath)) {// repository root
+                       xPathExpression = "/" + CrName.root.get() + '/' + relativePath;
+               } else {
+                       String documentNodeName = document.getDocumentElement().getNodeName();
+                       xPathExpression = '/' + documentNodeName + '/' + relativePath;
+               }
                try {
                        NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
                        return nodes;
index 37992072482e0cf36b1a08ed899de0afcf2f8601..6657e794e14f664ae07ec552b11324255c435723 100644 (file)
@@ -23,8 +23,8 @@ import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
 import org.argeo.api.cms.DataAdminPrincipal;
+import org.argeo.api.cms.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.auth.RemoteCmsSessionImpl;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.osgi.useradmin.AuthenticatingUser;
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java
deleted file mode 100644 (file)
index 8834f35..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.cms.auth;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.ArgeoNamespace;
-import org.argeo.api.acr.ContentName;
-import org.argeo.cms.SystemRole;
-
-/** Standard CMS system roles. */
-public enum CmsRole implements SystemRole {
-       userAdmin, //
-       groupAdmin, //
-       //
-       ;
-
-       private final static String QUALIFIER = "cms.";
-
-       private final ContentName name;
-
-       CmsRole() {
-               name = new ContentName(ArgeoNamespace.ROLE_NAMESPACE_URI, QUALIFIER + name());
-       }
-
-       @Override
-       public QName qName() {
-               return name;
-       }
-
-       @Override
-       public String toString() {
-               return name.toPrefixedString();
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsSystemRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsSystemRole.java
new file mode 100644 (file)
index 0000000..87daa2f
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.cms.auth;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.cms.auth.SystemRole;
+
+/** Standard CMS system roles. */
+public enum CmsSystemRole implements SystemRole {
+       userAdmin, //
+       groupAdmin, //
+       //
+       ;
+
+       private final static String QUALIFIER = "cms.";
+
+       private final ContentName name;
+
+       CmsSystemRole() {
+               name = new ContentName(ArgeoNamespace.ROLE_NAMESPACE_URI, QUALIFIER + name());
+       }
+
+       @Override
+       public QName qName() {
+               return name;
+       }
+
+       @Override
+       public String toString() {
+               return name.toPrefixedString();
+       }
+}
index cfffb6eea74e43a50dde96ff3e87e0288beb82b3..b6623f2559e3ce4d8c08f35b6629ed225f6fdae7 100644 (file)
@@ -76,6 +76,8 @@ public class SingleUserLoginModule implements LoginModule {
                        CmsAuthUtils.addAuthorization(subject, authorization);
                } else {
                        // next step with user admin will properly populate
+                       authorization = new SingleUserAuthorization(authorizationName);
+                       CmsAuthUtils.addAuthorization(subject, authorization);
                }
 
                // Add standard Java OS login
index bef6d7f0a187718c914fc6084f193a3aa7e29137..84e75e32515f0f14bfc3e9907c3949c9fada3e7d 100644 (file)
@@ -14,6 +14,8 @@ import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
 
 /** Centralise common patterns to manage users with a {@link UserAdmin} */
+@Deprecated
+// TODO use CmsRole after migrating to qualified properties
 public class UserAdminUtils {
 
        // CURRENTUSER HELPERS
@@ -73,6 +75,10 @@ public class UserAdminUtils {
                return getUserDisplayName(user);
        }
 
+       public static String getUserDisplayName(org.argeo.api.cms.directory.CmsRole user) {
+               return getUserDisplayName((Role) user);
+       }
+
        public static String getUserDisplayName(Role user) {
                String dName = getProperty(user, LdapAttr.displayName.name());
                if (isEmpty(dName))
index 5dffcb63aa61550cc7606e837c835c678a2ca5b7..39355c3c415248b16076547092cf2cad98a6729a 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.directory.ldap;
 
-import static org.argeo.cms.directory.ldap.LdapNameUtils.toLdapName;
+import static org.argeo.api.acr.ldap.LdapNameUtils.toLdapName;
 
 import java.io.File;
 import java.net.URI;
@@ -27,6 +27,7 @@ import javax.naming.ldap.Rdn;
 import javax.transaction.xa.XAResource;
 
 import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.directory.CmsDirectory;
 import org.argeo.api.cms.directory.HierarchyUnit;
index 94e0ac46def207abecdfeb66743cbc77e45002c2..f919fa6c843050d7a8564d133e91c3e91173c7db 100644 (file)
@@ -22,6 +22,7 @@ import javax.naming.directory.Attributes;
 import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.api.acr.QNamed;
 import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.directory.DirectoryDigestUtils;
@@ -125,9 +126,9 @@ public class DefaultLdapEntry implements LdapEntry {
                // modifiedAttributes = (Attributes) publishedAttributes.clone();
        }
 
-       public synchronized void publishAttributes(Attributes modifiedAttributes) {
-//             publishedAttributes = modifiedAttributes;
-       }
+//     public synchronized void publishAttributes(Attributes modifiedAttributes) {
+////           publishedAttributes = modifiedAttributes;
+//     }
 
        /*
         * PROPERTIES
@@ -145,6 +146,17 @@ public class DefaultLdapEntry implements LdapEntry {
                return credentials;
        }
 
+       protected String getPropertyAsString(QNamed key) {
+               return getPropertyAsString(key.localName());
+       }
+
+       protected String getPropertyAsString(String key) {
+               Object res = getProperties().get(key);
+               if (res == null)
+                       return null;
+               return res.toString();
+       }
+
        /*
         * CREDENTIALS
         */
index cdc1c9fe68bfbe0de3faf968460efd5f4bd973f2..ff785638d53a211a0eaaea88f04ab2ed7e28e552 100644 (file)
@@ -20,6 +20,7 @@ import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
 import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.directory.HierarchyUnit;
 
index fa95c961542d245598a83f5918e921cfc09b191f..798c449cf9b61c98e699bbe1c8c28b1742bacf48 100644 (file)
@@ -20,7 +20,7 @@ public interface LdapEntry {
 
        Attributes getAttributes();
 
-       void publishAttributes(Attributes modifiedAttributes);
+       //void publishAttributes(Attributes modifiedAttributes);
 
        List<LdapName> getReferences(String attributeId);
 
index b60ee0c68935cc3e08248f1a69902b8d6aebe393..81b20f0a33b2d12c99f40c2e675d9ff435014416 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Locale;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.cms.directory.HierarchyUnit;
 
 /** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java
deleted file mode 100644 (file)
index 74f23da..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.argeo.cms.directory.ldap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-/** Utilities to simplify using {@link LdapName}. */
-public class LdapNameUtils {
-
-       public static LdapName relativeName(LdapName prefix, LdapName dn) {
-               try {
-                       if (!dn.startsWith(prefix))
-                               throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
-                       LdapName res = (LdapName) dn.clone();
-                       for (int i = 0; i < prefix.size(); i++) {
-                               res.remove(0);
-                       }
-                       return res;
-               } catch (InvalidNameException e) {
-                       throw new IllegalStateException("Cannot find realtive name", e);
-               }
-       }
-
-       public static LdapName getParent(LdapName dn) {
-               try {
-                       LdapName parent = (LdapName) dn.clone();
-                       parent.remove(parent.size() - 1);
-                       return parent;
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot get parent of " + dn, e);
-               }
-       }
-
-       public static Rdn getParentRdn(LdapName dn) {
-               if (dn.size() < 2)
-                       throw new IllegalArgumentException(dn + " has no parent");
-               Rdn parentRdn = dn.getRdn(dn.size() - 2);
-               return parentRdn;
-       }
-
-       public static LdapName toLdapName(String distinguishedName) {
-               try {
-                       return new LdapName(distinguishedName);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
-               }
-       }
-
-       public static Rdn getLastRdn(LdapName dn) {
-               return dn.getRdn(dn.size() - 1);
-       }
-
-       public static String getLastRdnAsString(LdapName dn) {
-               return getLastRdn(dn).toString();
-       }
-
-       public static String getLastRdnValue(String dn) {
-               return getLastRdnValue(toLdapName(dn));
-       }
-
-       public static String getLastRdnValue(LdapName dn) {
-               return getLastRdn(dn).getValue().toString();
-       }
-
-       /** singleton */
-       private LdapNameUtils() {
-
-       }
-}
index 52148dfab6084f695b69dcbb8b9b2b79e3a71dc6..06489c6e2dbc052b433581579fbe309179a8842c 100644 (file)
@@ -257,10 +257,14 @@ public class LdifDao extends AbstractLdapDirectoryDao {
                        }
                        if (user == null)
                                throw new IllegalStateException("User to modify no found " + dn);
-                       user.publishAttributes(modifiedAttrs);
+                       publishAttributes(dn, modifiedAttrs);
                }
        }
 
+       protected void publishAttributes(LdapName dn, Attributes modifiedAttributes) {
+               values.put(dn, modifiedAttributes);
+       }
+
        @Override
        public void commit(LdapEntryWorkingCopy wc) {
                save();
index 49cc242c7589fe8a5492b541fdb006db226482f0..8c5ccb048c06e1f933ce21fb74eedc74f56b03a1 100644 (file)
@@ -20,7 +20,7 @@ import java.util.Map;
 import java.util.NavigableMap;
 import java.util.TreeMap;
 
-import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.acr.CmsContent;
 import org.argeo.cms.http.HttpHeader;
 import org.argeo.cms.http.HttpStatus;
 import org.argeo.cms.util.StreamUtils;
@@ -66,7 +66,7 @@ public class StaticHttpHandler implements HttpHandler {
                String mountPath = entry.getKey();
                if (!path.startsWith(mountPath)) {
                        // FIXME make it more robust and find when there is no content provider
-                       String[] parent = ContentUtils.getParentPath(path);
+                       String[] parent = CmsContent.getParentPath(path);
                        return findBind(parent[0]);
                }
                return entry;
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java
deleted file mode 100644 (file)
index 9e0ebce..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.internal.auth;
-
-import java.security.Principal;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.cms.RoleNameUtils;
-import org.osgi.service.useradmin.Authorization;
-
-/**
- * A {@link Principal} which has been implied by an {@link Authorization}. If it
- * is empty it means this is an additional identity, otherwise it lists the
- * users (typically the logged in user but possibly empty
- * {@link ImpliedByPrincipal}s) which have implied it. When an additional
- * identity is removed, the related {@link ImpliedByPrincipal}s can thus be
- * removed.
- */
-public final class ImpliedByPrincipal implements Principal {
-       private final String name;
-       private final QName roleName;
-       private final boolean systemRole;
-       private final String context;
-
-       private Set<Principal> causes = new HashSet<Principal>();
-
-       public ImpliedByPrincipal(String name, Principal userPrincipal) {
-               this.name = name;
-               roleName = RoleNameUtils.getLastRdnAsName(name);
-               systemRole = RoleNameUtils.isSystemRole(roleName);
-               context = RoleNameUtils.getContext(name);
-               if (userPrincipal != null)
-                       causes.add(userPrincipal);
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       /*
-        * OBJECT
-        */
-
-       public QName getRoleName() {
-               return roleName;
-       }
-
-       public String getContext() {
-               return context;
-       }
-
-       public boolean isSystemRole() {
-               return systemRole;
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof ImpliedByPrincipal) {
-                       ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
-                       // TODO check members too?
-                       return name.equals(that.name);
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return name.toString();
-       }
-}
index b09956203cc2eb4afa0bfa60b2a1c22cf1fe9f5c..94b08da4d316a37d7d614c74904c91c09a72ced3 100644 (file)
@@ -1,9 +1,10 @@
 package org.argeo.cms.internal.osgi;
 
 import java.security.AllPermission;
-import java.util.Dictionary;
 
-import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.uuid.NodeIdSupplier;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -19,27 +20,14 @@ import org.osgi.service.permissionadmin.PermissionInfo;
  * bundle (and only it)
  */
 public class CmsActivator implements BundleActivator {
-       private final static CmsLog log = CmsLog.getLog(CmsActivator.class);
+//     private final static CmsLog log = CmsLog.getLog(CmsActivator.class);
+       private final static String PROP_ARGEO_OSGI_PARENT_UUID = "argeo.osgi.parent.uuid";
 
        // TODO make it configurable
        private boolean hardened = false;
 
        private static BundleContext bundleContext;
 
-       void init() {
-       }
-
-       void destroy() {
-               try {
-                       bundleContext = null;
-//                     this.logReaderService = null;
-               } catch (Exception e) {
-                       log.error("CMS activator shutdown failed", e);
-               }
-
-               new GogoShellKiller().start();
-       }
-
        protected void initSecurity() {
                // code-level permissions
                String osgiSecurity = bundleContext.getProperty(Constants.FRAMEWORK_SECURITY);
@@ -76,14 +64,14 @@ public class CmsActivator implements BundleActivator {
 
        }
 
-       public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
-               if (bundleContext != null) {
-                       bundleContext.registerService(clss, service, properties);
-               }
-
-       }
-
-       public static <T> T getService(Class<T> clss) {
+//     static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
+//             if (bundleContext != null) {
+//                     bundleContext.registerService(clss, service, properties);
+//             }
+//
+//     }
+//
+       static <T> T getService(Class<T> clss) {
                if (bundleContext != null) {
                        return bundleContext.getService(bundleContext.getServiceReference(clss));
                } else {
@@ -98,20 +86,32 @@ public class CmsActivator implements BundleActivator {
        @Override
        public void start(BundleContext bc) throws Exception {
                bundleContext = bc;
-
-               init();
-
+               CmsStateImpl cmsState = new CmsStateImpl();
+               cmsState.start();
+               bundleContext.registerService(new String[] { CmsState.class.getName(), NodeIdSupplier.class.getName() },
+                               cmsState, null);
        }
 
        @Override
        public void stop(BundleContext bc) throws Exception {
-
-               destroy();
-               bundleContext = null;
+               try {
+                       CmsStateImpl cmsState = (CmsStateImpl) getService(CmsState.class);
+                       String parentFrameworkuuid = bc.getProperty(PROP_ARGEO_OSGI_PARENT_UUID);
+                       if (parentFrameworkuuid == null)
+                               new GogoShellKiller().start();
+                       cmsState.stop();
+               } finally {
+                       bundleContext = null;
+               }
        }
 
-       public static BundleContext getBundleContext() {
+       static BundleContext getBundleContext() {
                return bundleContext;
        }
 
+       public static String getFrameworkProperty(String key) {
+               if (bundleContext == null)
+                       return null;
+               return getBundleContext().getProperty(key);
+       }
 }
index 85f045bae660b6783d2aa4448e7fdce59af327e1..6da1cdd8a6870f5b567f33f89de1daa5cabab9c9 100644 (file)
@@ -9,7 +9,6 @@ import org.argeo.cms.runtime.DirectoryConf;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.log.LogEntry;
 import org.osgi.service.log.LogLevel;
 import org.osgi.service.log.LogListener;
@@ -19,38 +18,8 @@ import org.osgi.service.log.LogReaderService;
 public class CmsOsgiLogger implements LogListener {
        private final static String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
        private final static String CONTEXT_NAME_PROP = "contextName";
-       
-       private LogReaderService logReaderService;
-
-//     /** Internal debug for development purposes. */
-//     private static Boolean debug = false;
-
-//     private Boolean disabled = false;
-//
-//     private String level = null;
-
-//     private Level log4jLevel = null;
-
-//     private Properties configuration;
-
-//     private AppenderImpl appender;
 
-//     private BlockingQueue<LogEvent> events;
-//     private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
-
-//     private Integer maxLastEventsCount = 10 * 1000;
-//
-//     /** Marker to prevent stack overflow */
-//     private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
-//
-//             @Override
-//             protected Boolean initialValue() {
-//                     return false;
-//             }
-//     };
-
-//     public CmsOsgiLogger(LogReaderService lrs) {
-//     }
+       private LogReaderService logReaderService;
 
        public void start() {
                if (logReaderService != null) {
@@ -58,51 +27,10 @@ public class CmsOsgiLogger implements LogListener {
                        while (logEntries.hasMoreElements())
                                logged(logEntries.nextElement());
                        logReaderService.addLogListener(this);
-
-                       // configure log4j watcher
-//                     String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
-//                     if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
-//                             if (log4jConfiguration.contains("..")) {
-//                                     if (log4jConfiguration.startsWith("file://"))
-//                                             log4jConfiguration = log4jConfiguration.substring("file://".length());
-//                                     else if (log4jConfiguration.startsWith("file:"))
-//                                             log4jConfiguration = log4jConfiguration.substring("file:".length());
-//                             }
-//                             try {
-//                                     Path log4jconfigPath;
-//                                     if (log4jConfiguration.startsWith("file:"))
-//                                             log4jconfigPath = Paths.get(new URI(log4jConfiguration));
-//                                     else
-//                                             log4jconfigPath = Paths.get(log4jConfiguration);
-//                                     Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
-//                                     log4jConfWatcher.start();
-//                             } catch (Exception e) {
-//                                     stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
-//                             }
-//                     }
                }
-//             try {
-////                   events = new LinkedBlockingQueue<LogEvent>();
-////
-////                   // if (layout != null)
-////                   // setLayout(layout);
-////                   // else
-////                   // setLayout(new PatternLayout(pattern));
-//////                 appender = new AppenderImpl();
-////                   reloadConfiguration();
-//////                 Logger.getRootLogger().addAppender(appender);
-////
-////                   logDispatcherThread = new LogDispatcherThread();
-////                   logDispatcherThread.start();
-//             } catch (Exception e) {
-//                     throw new IllegalStateException("Cannot initialize log4j");
-//             }
        }
 
        public void stop() throws Exception {
-//             events.clear();
-//             events = null;
-//             logDispatcherThread.interrupt();
                logReaderService.removeLogListener(this);
        }
 
@@ -115,7 +43,10 @@ public class CmsOsgiLogger implements LogListener {
        //
        @Override
        public void logged(LogEntry status) {
-               CmsLog pluginLog = CmsLog.getLog(status.getBundle().getSymbolicName());
+               String loggerName = status.getBundle().getSymbolicName();
+               if (loggerName == null)
+                       loggerName = "org.argeo.ext.osgi";
+               CmsLog pluginLog = CmsLog.getLog(loggerName);
                LogLevel severity = status.getLogLevel();
                if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) {
                        // FIXME Fix Argeo TP
@@ -160,9 +91,9 @@ public class CmsOsgiLogger implements LogListener {
                        Object cn = sr.getProperty(CmsConstants.CN);
                        if (cn != null)
                                sb.append(" " + CmsConstants.CN + ": " + cn);
-                       Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
-                       if (factoryPid != null)
-                               sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
+//                     Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
+//                     if (factoryPid != null)
+//                             sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
                        // else {
                        // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
                        // if (servicePid != null)
@@ -216,296 +147,4 @@ public class CmsOsgiLogger implements LogListener {
                this.logReaderService = logReaderService;
        }
 
-       
-       //
-       // ARGEO LOGGER
-       //
-
-//     public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
-//             String username = CurrentUser.getUsername();
-//             if (username == null)
-//                     throw new IllegalStateException("Only authenticated users can register a log listener");
-//
-//             if (!userListeners.containsKey(username)) {
-//                     List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
-//                     userListeners.put(username, lst);
-//             }
-//             userListeners.get(username).add(listener);
-//             List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
-//             for (LogEvent evt : lastEvents)
-//                     dispatchEvent(listener, evt);
-//     }
-//
-//     public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
-//                     boolean everything) {
-//             if (everything)
-//                     everythingListeners.add(listener);
-//             else
-//                     allUsersListeners.add(listener);
-//             List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
-//             for (LogEvent evt : lastEvents)
-//                     if (everything || evt.getUsername() != null)
-//                             dispatchEvent(listener, evt);
-//     }
-//
-//     public synchronized void unregister(ArgeoLogListener listener) {
-//             String username = CurrentUser.getUsername();
-//             if (username == null)// FIXME
-//                     return;
-//             if (!userListeners.containsKey(username))
-//                     throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
-//             if (!userListeners.get(username).contains(listener))
-//                     throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
-//             userListeners.get(username).remove(listener);
-//             if (userListeners.get(username).isEmpty())
-//                     userListeners.remove(username);
-//
-//     }
-//
-//     public synchronized void unregisterForAll(ArgeoLogListener listener) {
-//             everythingListeners.remove(listener);
-//             allUsersListeners.remove(listener);
-//     }
-
-//     /** For development purpose, since using regular logging is not easy here */
-//     private static void stdOut(Object obj) {
-//             System.out.println(obj);
-//     }
-//
-//     private static void stdErr(Object obj) {
-//             System.err.println(obj);
-//     }
-//
-//     private static void debug(Object obj) {
-//             if (debug)
-//                     System.out.println(obj);
-//     }
-//
-//     private static boolean isInternalDebugEnabled() {
-//             return debug;
-//     }
-
-       // public void setPattern(String pattern) {
-       // this.pattern = pattern;
-       // }
-
-//     public void setDisabled(Boolean disabled) {
-//             this.disabled = disabled;
-//     }
-//
-//     public void setLevel(String level) {
-//             this.level = level;
-//     }
-
-//     public void setConfiguration(Properties configuration) {
-//             this.configuration = configuration;
-//     }
-//
-//     public void updateConfiguration(Properties configuration) {
-//             setConfiguration(configuration);
-//             reloadConfiguration();
-//     }
-//
-//     public Properties getConfiguration() {
-//             return configuration;
-//     }
-//
-//     /**
-//      * Reloads configuration (if the configuration {@link Properties} is set)
-//      */
-//     protected void reloadConfiguration() {
-//             if (configuration != null) {
-////                   LogManager.resetConfiguration();
-////                   PropertyConfigurator.configure(configuration);
-//             }
-//     }
-
-//     protected synchronized void processLoggingEvent(LogEvent event) {
-//             if (disabled)
-//                     return;
-//
-//             if (dispatching.get())
-//                     return;
-//
-//             if (level != null && !level.trim().equals("")) {
-////                   if (log4jLevel == null || !log4jLevel.toString().equals(level))
-////                           try {
-////                                   log4jLevel = Level.toLevel(level);
-////                           } catch (Exception e) {
-////                                   System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
-////                                   e.printStackTrace();
-////                                   level = null;
-////                           }
-////
-////                   if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
-////                           return;
-////                   }
-//             }
-//
-////           try {
-////                   // admin listeners
-////                   Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
-////                   while (everythingIt.hasNext())
-////                           dispatchEvent(everythingIt.next(), event);
-////
-////                   if (event.getUsername() != null) {
-////                           Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
-////                           while (allUsersIt.hasNext())
-////                                   dispatchEvent(allUsersIt.next(), event);
-////
-////                           if (userListeners.containsKey(event.getUsername())) {
-////                                   Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
-////                                   while (userIt.hasNext())
-////                                           dispatchEvent(userIt.next(), event);
-////                           }
-////                   }
-////           } catch (Exception e) {
-////                   stdOut("Cannot process logging event");
-////                   e.printStackTrace();
-////           }
-//     }
-
-//     protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
-////           LoggingEvent event = evt.getLoggingEvent();
-////           logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
-////                           event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
-//     }
-
-//     private class AppenderImpl { // extends AppenderSkeleton {
-//             public boolean requiresLayout() {
-//                     return false;
-//             }
-//
-//             public void close() {
-//             }
-//
-////           @Override
-////           protected void append(LoggingEvent event) {
-////                   if (events != null) {
-////                           try {
-////                                   String username = CurrentUser.getUsername();
-////                                   events.put(new LogEvent(username, event));
-////                           } catch (InterruptedException e) {
-////                                   // silent
-////                           }
-////                   }
-////           }
-//
-//     }
-
-//     private class LogDispatcherThread extends Thread {
-//             /** encapsulated in order to simplify concurrency management */
-//             private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
-//
-//             public LogDispatcherThread() {
-//                     super("Argeo Logging Dispatcher Thread");
-//             }
-//
-//             public void run() {
-//                     while (events != null) {
-//                             try {
-//                                     LogEvent loggingEvent = events.take();
-//                                     processLoggingEvent(loggingEvent);
-//                                     addLastEvent(loggingEvent);
-//                             } catch (InterruptedException e) {
-//                                     if (events == null)
-//                                             return;
-//                             }
-//                     }
-//             }
-//
-//             protected synchronized void addLastEvent(LogEvent loggingEvent) {
-//                     if (lastEvents.size() >= maxLastEventsCount)
-//                             lastEvents.poll();
-//                     lastEvents.add(loggingEvent);
-//             }
-//
-//             public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
-//                     LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
-//                     ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
-//                     int count = 0;
-//                     while (it.hasPrevious() && (count < maxCount)) {
-//                             LogEvent evt = it.previous();
-//                             if (username == null || username.equals(evt.getUsername())) {
-//                                     evts.push(evt);
-//                                     count++;
-//                             }
-//                     }
-//                     return evts;
-//             }
-//     }
-
-//     private class LogEvent {
-//             private final String username;
-////           private final LoggingEvent loggingEvent;
-//
-//             public LogEvent(String username) {
-//                     super();
-//                     this.username = username;
-////                   this.loggingEvent = loggingEvent;
-//             }
-//
-////           @Override
-////           public int hashCode() {
-////                   return loggingEvent.hashCode();
-////           }
-////
-////           @Override
-////           public boolean equals(Object obj) {
-////                   return loggingEvent.equals(obj);
-////           }
-////
-////           @Override
-////           public String toString() {
-////                   return username + "@ " + loggingEvent.toString();
-////           }
-//
-//             public String getUsername() {
-//                     return username;
-//             }
-//
-////           public LoggingEvent getLoggingEvent() {
-////                   return loggingEvent;
-////           }
-//
-//     }
-//
-//     private class Log4jConfWatcherThread extends Thread {
-//             private Path log4jConfigurationPath;
-//
-//             public Log4jConfWatcherThread(Path log4jConfigurationPath) {
-//                     super("Log4j Configuration Watcher");
-//                     try {
-//                             this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
-//                     } catch (IOException e) {
-//                             this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
-//                             stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
-//                     }
-//             }
-//
-//             public void run() {
-//                     Path parentDir = log4jConfigurationPath.getParent();
-//                     try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
-//                             parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
-//                             WatchKey wk;
-//                             watching: while ((wk = watchService.take()) != null) {
-//                                     for (WatchEvent<?> event : wk.pollEvents()) {
-//                                             final Path changed = (Path) event.context();
-//                                             if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
-//                                                     if (isInternalDebugEnabled())
-//                                                             debug(log4jConfigurationPath + " has changed, reloading.");
-////                                                   PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
-//                                             }
-//                                     }
-//                                     // reset the key
-//                                     boolean valid = wk.reset();
-//                                     if (!valid) {
-//                                             break watching;
-//                                     }
-//                             }
-//                     } catch (IOException | InterruptedException e) {
-//                             stdErr("Log4j configuration watcher failed: " + e.getMessage());
-//                     }
-//             }
-//     }
 }
index 9de7a4fd13f081497909bc6c08041e321a433400..332f5cb8c0b72d2cf68eba0d4520893276c05220 100644 (file)
@@ -30,7 +30,7 @@ class GogoShellKiller extends Thread {
                        return;
                System.exit(0);
                // No non-deamon threads left, forcibly halting the VM
-               //Runtime.getRuntime().halt(0);
+               // Runtime.getRuntime().halt(0);
        }
 
        private ThreadGroup getRootThreadGroup(ThreadGroup tg) {
@@ -56,7 +56,7 @@ class GogoShellKiller extends Thread {
                Thread[] threads = new Thread[rootThreadGroup.activeCount()];
                rootThreadGroup.enumerate(threads, true);
                for (Thread thread : threads) {
-                       if (thread.getName().equals("pipe-gosh --login --noshutdown"))
+                       if (thread.getName() != null && thread.getName().equals("pipe-gosh --login --noshutdown"))
                                return thread;
                }
                return null;
index 7055538c33ee1bc8d2d57e4878a52917ed8dd645..ae29bd9300c4851c2d08b88911c64b120254a3f6 100644 (file)
@@ -13,7 +13,6 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServicePermission;
-import org.osgi.service.cm.ConfigurationPermission;
 import org.osgi.service.condpermadmin.BundleLocationCondition;
 import org.osgi.service.condpermadmin.ConditionInfo;
 import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
@@ -125,20 +124,22 @@ public interface SecurityProfile {
 
                // DS
                Bundle dsBundle = findBundle("org.eclipse.equinox.ds");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { dsBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "xml.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "accessDeclaredMembers", null),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
-                                               new PermissionInfo(ReflectPermission.class.getName(), "suppressAccessChecks", null), },
-                               ConditionalPermissionInfo.ALLOW));
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { dsBundle.getLocation() }) },
+                                               new PermissionInfo[] {
+                                                               new PermissionInfo("org.osgi.service.cm.ConfigurationPermission", "*", "configure"),
+                                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+                                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+                                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
+                                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
+                                                               new PermissionInfo(PropertyPermission.class.getName(), "xml.*", "read"),
+                                                               new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
+                                                               new PermissionInfo(RuntimePermission.class.getName(), "accessDeclaredMembers", null),
+                                                               new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
+                                                               new PermissionInfo(ReflectPermission.class.getName(), "suppressAccessChecks", null), },
+                                               ConditionalPermissionInfo.ALLOW));
 
                // Jetty
                // Bundle jettyUtilBundle = findBundle("org.eclipse.equinox.http.jetty");
index 01d285b8cfd2767b8d4252b5cb93cb30861950a9..25f8dad6a784f1c496b6ff3a2b2efaa941e2512d 100644 (file)
@@ -96,7 +96,8 @@ public class CmsContextImpl implements CmsContext {
                        availableSince = System.currentTimeMillis();
                        long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
                        String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-                       log.info("## ARGEO CMS AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
+                       log.info("## ARGEO CMS " + cmsState.getUuid() + " AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "")
+                                       + " ##");
                        if (log.isDebugEnabled()) {
                                log.debug("## state: " + state);
                                if (data != null)
index e1c420b8287469bb698af791d04b9d87638563d9..d3f4b575aa7ff6486f5708a7fbfa93728af4bb8c 100644 (file)
@@ -63,12 +63,21 @@ public class CmsDeploymentImpl implements CmsDeployment {
 
        public void setHttpServer(HttpServer httpServer) {
                Objects.requireNonNull(httpServer);
-               this.httpServer.complete(httpServer);
-               // create contexts whose handles had already been published
-               for (String contextPath : httpHandlers.keySet()) {
-                       HttpHandler httpHandler = httpHandlers.get(contextPath);
-                       CmsAuthenticator authenticator = httpAuthenticators.get(contextPath);
-                       createHttpContext(contextPath, httpHandler, authenticator);
+               if (this.httpServer.isDone())
+                       if (httpExpected)
+                               throw new IllegalStateException("HTTP server is already set");
+                       else
+                               return;// ignore
+               // create contexts whose handlers had already been published
+               synchronized (httpHandlers) {
+                       synchronized (httpAuthenticators) {
+                               this.httpServer.complete(httpServer);
+                               for (String contextPath : httpHandlers.keySet()) {
+                                       HttpHandler httpHandler = httpHandlers.get(contextPath);
+                                       CmsAuthenticator authenticator = httpAuthenticators.get(contextPath);
+                                       createHttpContext(contextPath, httpHandler, authenticator);
+                               }
+                       }
                }
        }
 
@@ -80,9 +89,13 @@ public class CmsDeploymentImpl implements CmsDeployment {
                }
                boolean isPublic = Boolean.parseBoolean(properties.get(CmsConstants.CONTEXT_PUBLIC));
                CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator();
-               httpHandlers.put(contextPath, httpHandler);
-               httpAuthenticators.put(contextPath, authenticator);
-               if (httpServer.join() == null) {
+               synchronized (httpHandlers) {
+                       synchronized (httpAuthenticators) {
+                               httpHandlers.put(contextPath, httpHandler);
+                               httpAuthenticators.put(contextPath, authenticator);
+                       }
+               }
+               if (!httpServer.isDone()) {
                        return;
                } else {
                        createHttpContext(contextPath, httpHandler, authenticator);
@@ -95,6 +108,9 @@ public class CmsDeploymentImpl implements CmsDeployment {
                                log.warn("Ignore HTTP context " + contextPath + " as we don't provide an HTTP server");
                        return;
                }
+               if (!this.httpServer.isDone())
+                       throw new IllegalStateException("HTTP server is not set");
+               // TODO use resultNow when switching to Java 21
                HttpContext httpContext = httpServer.join().createContext(contextPath);
                // we want to set the authenticator BEFORE the handler actually becomes active
                httpContext.setAuthenticator(authenticator);
@@ -107,8 +123,9 @@ public class CmsDeploymentImpl implements CmsDeployment {
                if (contextPath == null)
                        return; // ignore silently
                httpHandlers.remove(contextPath);
-               if (httpServer.join() == null)
+               if (!httpExpected || !httpServer.isDone())
                        return;
+               // TODO use resultNow when switching to Java 21
                httpServer.join().removeContext(contextPath);
                log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
        }
index 60a51b44f7f8a8f913f8008e11460b2a956114f1..2758ba9b191bf08693c37d44ff457f0240622e84 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.cms.internal.runtime;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.Reader;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.URL;
 import java.net.UnknownHostException;
@@ -37,16 +38,18 @@ import javax.security.auth.login.Configuration;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
-import org.argeo.api.uuid.UuidFactory;
+import org.argeo.api.uuid.NodeIdSupplier;
+import org.argeo.api.uuid.UuidBinaryUtils;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.auth.ident.IdentClient;
+import org.argeo.cms.util.DigestUtils;
 import org.argeo.cms.util.FsUtils;
 import org.argeo.cms.util.OS;
 
 /**
  * Implementation of a {@link CmsState}, initialising the required services.
  */
-public class CmsStateImpl implements CmsState {
+public class CmsStateImpl implements CmsState, NodeIdSupplier {
        private final static CmsLog log = CmsLog.getLog(CmsStateImpl.class);
 
        // REFERENCES
@@ -55,8 +58,9 @@ public class CmsStateImpl implements CmsState {
        private UUID uuid;
 //     private final boolean cleanState;
        private String hostname;
+       private InetAddress inetAddress;
 
-       private UuidFactory uuidFactory;
+//     private UuidFactory uuidFactory;
 
        private final Map<CmsDeployProperty, String> deployPropertyDefaults;
 
@@ -111,7 +115,8 @@ public class CmsStateImpl implements CmsState {
                        if (log.isTraceEnabled())
                                log.trace("CMS State started");
 
-                       this.uuid = uuidFactory.timeUUID();
+                       String frameworkUuid = KernelUtils.getFrameworkProp(KernelUtils.OSGI_FRAMEWORK_UUID);
+                       this.uuid = frameworkUuid != null ? UUID.fromString(frameworkUuid) : UUID.randomUUID();
 
                        // hostname
                        this.hostname = getDeployProperty(CmsDeployProperty.HOST);
@@ -120,7 +125,8 @@ public class CmsStateImpl implements CmsState {
                                final String LOCALHOST_IP = "::1";
                                ForkJoinTask<String> hostnameFJT = ForkJoinPool.commonPool().submit(() -> {
                                        try {
-                                               String hostname = InetAddress.getLocalHost().getHostName();
+                                               this.inetAddress = InetAddress.getLocalHost();
+                                               String hostname = this.inetAddress.getHostName();
                                                return hostname;
                                        } catch (UnknownHostException e) {
                                                throw new IllegalStateException("Cannot get local hostname", e);
@@ -132,6 +138,16 @@ public class CmsStateImpl implements CmsState {
                                        this.hostname = LOCALHOST_IP;
                                        log.warn("Could not get local hostname, using " + this.hostname);
                                }
+                       } else {
+                               InetAddress[] addresses = InetAddress.getAllByName(this.hostname);
+                               InetAddress selectedAddr = null;
+                               addresses: for (InetAddress addr : addresses) {
+                                       if (selectedAddr == null)
+                                               selectedAddr = addr;
+                                       if (selectedAddr instanceof Inet6Address)
+                                               break addresses;
+                               }
+                               this.inetAddress = selectedAddr;
                        }
 
                        availableSince = System.currentTimeMillis();
@@ -154,7 +170,7 @@ public class CmsStateImpl implements CmsState {
                                                }
                                        }
                                }
-                               log.debug("## CMS starting... (" + uuid + ")\n" + sb + "\n");
+                               log.debug("## CMS starting on " + hostname + " ... (" + uuid + ")\n" + sb + "\n");
                        }
 
                        if (log.isTraceEnabled()) {
@@ -169,6 +185,7 @@ public class CmsStateImpl implements CmsState {
 
                } catch (RuntimeException | IOException e) {
                        log.error("## FATAL: CMS state failed", e);
+                       throw new IllegalStateException(e);
                }
        }
 
@@ -259,7 +276,8 @@ public class CmsStateImpl implements CmsState {
                        log.debug("CMS stopping...  (" + this.uuid + ")");
 
                long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
-               log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
+               log.info("## ARGEO CMS " + uuid + " STOPPED after " + (duration / 60) + "h " + (duration % 60)
+                               + "min uptime ##");
        }
 
        private void firstInit() throws IOException {
@@ -396,6 +414,77 @@ public class CmsStateImpl implements CmsState {
                return availableSince;
        }
 
+       /*
+        * NodeID supplier
+        */
+
+       @Override
+       public Long get() {
+               return NodeIdSupplier.toNodeIdBase(getIpBytes());
+       }
+
+       /** Returns an SHA1 digest of one of the IP addresses. */
+       protected byte[] getIpBytes() {
+//             Enumeration<NetworkInterface> netInterfaces = null;
+//             try {
+//                     netInterfaces = NetworkInterface.getNetworkInterfaces();
+//             } catch (SocketException e) {
+//                     throw new IllegalStateException(e);
+//             }
+//
+//             InetAddress selectedIpv6 = null;
+//             InetAddress selectedIpv4 = null;
+//             if (netInterfaces != null) {
+//                     netInterfaces: while (netInterfaces.hasMoreElements()) {
+//                             NetworkInterface netInterface = netInterfaces.nextElement();
+//                             byte[] hardwareAddress = null;
+//                             try {
+//                                     hardwareAddress = netInterface.getHardwareAddress();
+//                                     if (hardwareAddress != null) {
+//                                             // first IPv6
+//                                             addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+//                                                     InetAddress ip = addr.getAddress();
+//                                                     if (ip instanceof Inet6Address) {
+//                                                             Inet6Address ipv6 = (Inet6Address) ip;
+//                                                             if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
+//                                                                     continue addr;
+//                                                             selectedIpv6 = ipv6;
+//                                                             break netInterfaces;
+//                                                     }
+//
+//                                             }
+//                                             // then IPv4
+//                                             addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+//                                                     InetAddress ip = addr.getAddress();
+//                                                     if (ip instanceof Inet4Address) {
+//                                                             Inet4Address ipv4 = (Inet4Address) ip;
+//                                                             if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
+//                                                                     continue addr;
+//                                                             selectedIpv4 = ipv4;
+//                                                             // we keep searching for IPv6
+//                                                     }
+//
+//                                             }
+//                                     }
+//                             } catch (SocketException e) {
+//                                     throw new IllegalStateException(e);
+//                             }
+//                     }
+//             }
+//             InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
+               if (this.inetAddress.isLoopbackAddress()) {
+                       log.warn("No IP address found, using a random node id for UUID generation");
+                       return NodeIdSupplier.randomNodeId();
+               }
+               InetAddress selectedIp = this.inetAddress;
+               byte[] digest = DigestUtils.sha1(selectedIp.getAddress());
+               log.debug("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id");
+               byte[] nodeId = NodeIdSupplier.toNodeIdBytes(digest, 0);
+               // marks that this is not based on MAC address
+               NodeIdSupplier.forceToNoMacAddress(nodeId, 0);
+               return nodeId;
+       }
+
        /*
         * ACCESSORS
         */
@@ -404,9 +493,9 @@ public class CmsStateImpl implements CmsState {
                return uuid;
        }
 
-       public void setUuidFactory(UuidFactory uuidFactory) {
-               this.uuidFactory = uuidFactory;
-       }
+//     public void setUuidFactory(UuidFactory uuidFactory) {
+//             this.uuidFactory = uuidFactory;
+//     }
 
        public String getHostname() {
                return hostname;
@@ -452,4 +541,5 @@ public class CmsStateImpl implements CmsState {
                // TODO make passphrase more configurable
                return new IdentClient(remoteAddr);
        }
+
 }
index 1eb227b0e6588e755b0fe8e77f62f42a79d915d4..64d3fab2583e8ffb7259133f894f00044f23ccb0 100644 (file)
@@ -31,6 +31,7 @@ import org.argeo.api.acr.ldap.NamingUtils;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.api.cms.directory.CmsUser;
 import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
@@ -85,15 +86,19 @@ public class CmsUserManagerImpl implements CmsUserManager {
 
        }
 
-       @Override
-       public String getMyMail() {
-               return getUserMail(CurrentUser.getUsername());
-       }
+//     @Override
+//     public String getMyMail() {
+//             return getUserMail(CurrentUser.getUsername());
+//     }
 
        @Override
-       public Role[] getRoles(String filter) {
+       public CmsRole[] getRoles(String filter) {
                try {
-                       return userAdmin.getRoles(filter);
+                       Role[] roles = userAdmin.getRoles(filter);
+                       CmsRole[] res = new CmsRole[roles.length];
+                       for (int i = 0; i < roles.length; i++)
+                               res[i] = (CmsRole) roles[i];
+                       return res;
                } catch (InvalidSyntaxException e) {
                        throw new IllegalArgumentException("Invalid filter " + filter, e);
                }
@@ -124,7 +129,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
        /** Lists all roles of the given user */
        @Override
        public String[] getUserRoles(String dn) {
-               Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
+               Authorization currAuth = getUserAdmin().getAuthorization((User) getUser(dn));
                return currAuth.getRoles();
        }
 
@@ -147,28 +152,12 @@ public class CmsUserManagerImpl implements CmsUserManager {
                return users;
        }
 
-//     @Override
-//     public Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep) {
-//             if(!hierarchyUnit.isFunctional())
-//                     throw new IllegalArgumentException("Hierarchy unit "+hierarchyUnit.getBase()+" is not functional");
-//             UserDirectory directory = (UserDirectory)hierarchyUnit.getDirectory();
-//             Set<User> res = new HashSet<>();
-//             for(HierarchyUnit technicalHu:hierarchyUnit.getDirectHierarchyUnits(false)) {
-//                     if(technicalHu.isFunctional())
-//                             continue;
-//                     for(Role role:directory.getHierarchyUnitRoles(technicalHu, null, false)) {
-//                             if(role)
-//                     }
-//             }
-//             return res;
-//     }
-
        /** Recursively add users to list */
        private void addUsers(Set<CmsUser> users, Group group, String filter) {
-               Role[] roles = group.getMembers();
+               Role[] roles = ((Group) group).getMembers();
                for (Role role : roles) {
                        if (role.getType() == Role.GROUP) {
-                               addUsers(users, (CmsGroup) role, filter);
+                               addUsers(users, (Group) role, filter);
                        } else if (role.getType() == Role.USER) {
                                if (match(role, filter))
                                        users.add((CmsUser) role);
@@ -240,7 +229,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
        public CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials) {
                try {
                        userTransaction.begin();
-                       CmsUser user = (CmsUser) userAdmin.createRole(username, Role.USER);
+                       User user = (User) userAdmin.createRole(username, Role.USER);
                        if (properties != null) {
                                for (String key : properties.keySet())
                                        user.getProperties().put(key, properties.get(key));
@@ -250,7 +239,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                                        user.getCredentials().put(key, credentials.get(key));
                        }
                        userTransaction.commit();
-                       return user;
+                       return (CmsUser) user;
                } catch (Exception e) {
                        try {
                                userTransaction.rollback();
@@ -355,7 +344,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
        }
 
        @Override
-       public void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties) {
+       public void addObjectClasses(CmsRole role, Set<String> objectClasses, Map<String, Object> additionalProperties) {
                try {
                        userTransaction.begin();
                        LdapEntry.addObjectClasses(role.getProperties(), objectClasses);
@@ -417,10 +406,10 @@ public class CmsUserManagerImpl implements CmsUserManager {
        }
 
        @Override
-       public void addMember(CmsGroup group, Role role) {
+       public void addMember(CmsGroup group, CmsRole role) {
                try {
                        userTransaction.begin();
-                       group.addMember(role);
+                       ((Group) group).addMember((Role) role);
                        userTransaction.commit();
                } catch (Exception e1) {
                        try {
@@ -435,10 +424,10 @@ public class CmsUserManagerImpl implements CmsUserManager {
        }
 
        @Override
-       public void removeMember(CmsGroup group, Role role) {
+       public void removeMember(CmsGroup group, CmsRole role) {
                try {
                        userTransaction.begin();
-                       group.removeMember(role);
+                       ((Group) group).removeMember((Role) role);
                        userTransaction.commit();
                } catch (Exception e1) {
                        try {
@@ -675,7 +664,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
        }
 
        @Override
-       public UserDirectory getDirectory(Role user) {
+       public UserDirectory getDirectory(CmsRole user) {
                String name = user.getName();
                NavigableMap<String, UserDirectory> possible = new TreeMap<>();
                for (UserDirectory userDirectory : userDirectories) {
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUuidFactory.java
new file mode 100644 (file)
index 0000000..4077838
--- /dev/null
@@ -0,0 +1,90 @@
+package org.argeo.cms.internal.runtime;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.BitSet;
+import java.util.Enumeration;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.uuid.ConcurrentUuidFactory;
+import org.argeo.api.uuid.NodeIdSupplier;
+import org.argeo.api.uuid.UuidBinaryUtils;
+
+@Deprecated
+class CmsUuidFactory extends ConcurrentUuidFactory {
+       private final static CmsLog log = CmsLog.getLog(CmsUuidFactory.class);
+
+       public CmsUuidFactory(byte[] nodeId) {
+               super(0, nodeId);
+               assert createTimeUUID().node() == BitSet.valueOf(NodeIdSupplier.toNodeIdBytes(nodeId, 0)).toLongArray()[0];
+       }
+
+       public CmsUuidFactory() {
+               this(getIpBytes());
+       }
+
+       /** Returns an SHA1 digest of one of the IP addresses. */
+       protected static byte[] getIpBytes() {
+               Enumeration<NetworkInterface> netInterfaces = null;
+               try {
+                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+               } catch (SocketException e) {
+                       throw new IllegalStateException(e);
+               }
+
+               InetAddress selectedIpv6 = null;
+               InetAddress selectedIpv4 = null;
+               if (netInterfaces != null) {
+                       netInterfaces: while (netInterfaces.hasMoreElements()) {
+                               NetworkInterface netInterface = netInterfaces.nextElement();
+                               byte[] hardwareAddress = null;
+                               try {
+                                       hardwareAddress = netInterface.getHardwareAddress();
+                                       if (hardwareAddress != null) {
+                                               // first IPv6
+                                               addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                                       InetAddress ip = addr.getAddress();
+                                                       if (ip instanceof Inet6Address) {
+                                                               Inet6Address ipv6 = (Inet6Address) ip;
+                                                               if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
+                                                                       continue addr;
+                                                               selectedIpv6 = ipv6;
+                                                               break netInterfaces;
+                                                       }
+
+                                               }
+                                               // then IPv4
+                                               addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                                       InetAddress ip = addr.getAddress();
+                                                       if (ip instanceof Inet4Address) {
+                                                               Inet4Address ipv4 = (Inet4Address) ip;
+                                                               if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
+                                                                       continue addr;
+                                                               selectedIpv4 = ipv4;
+                                                               // we keep searching for IPv6
+                                                       }
+
+                                               }
+                                       }
+                               } catch (SocketException e) {
+                                       throw new IllegalStateException(e);
+                               }
+                       }
+               }
+               InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
+               if (selectedIp == null) {
+                       log.warn("No IP address found, using a random node id for UUID generation");
+                       return NodeIdSupplier.randomNodeId();
+               }
+               byte[] digest = sha1(selectedIp.getAddress());
+               log.debug("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id");
+               byte[] nodeId = NodeIdSupplier.toNodeIdBytes(digest, 0);
+               // marks that this is not based on MAC address
+               NodeIdSupplier.forceToNoMacAddress(nodeId, 0);
+               return nodeId;
+       }
+}
index b35b4be09311b8392531f1fe2e6ae306bc88adf9..db33ff9d407bb2fcdbb0ae3cd470954ba6732753 100644 (file)
@@ -22,6 +22,8 @@ class KernelUtils implements KernelConstants {
        final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
        final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
 
+       final static String OSGI_FRAMEWORK_UUID = "org.osgi.framework.uuid";
+
        static void setJaasConfiguration(URL jaasConfigurationUrl) {
                try {
                        URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
@@ -94,9 +96,8 @@ class KernelUtils implements KernelConstants {
 
        static String getFrameworkProp(String key, String def) {
                String value;
-               if (CmsActivator.getBundleContext() != null)
-                       value = CmsActivator.getBundleContext().getProperty(key);
-               else
+               value = CmsActivator.getFrameworkProperty(key);
+               if (value == null)
                        value = System.getProperty(key);
                if (value == null)
                        return def;
index 72c4336e31abd40c098a2e476ad719755ad0edfc..c5cb904fa2191594dae3465fb4ab17aca27b9d74 100644 (file)
@@ -12,7 +12,7 @@ import org.argeo.api.cms.directory.CmsAuthorization;
 import org.osgi.service.useradmin.Authorization;
 
 /** An {@link Authorization} which combines roles form various auth sources. */
-class AggregatingAuthorization implements CmsAuthorization {
+class AggregatingAuthorization implements CmsAuthorization, Authorization {
        private final String name;
        private final String displayName;
        private final Set<String> systemRoles;
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiAuthorization.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiAuthorization.java
new file mode 100644 (file)
index 0000000..1f2d215
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.osgi.useradmin;
+
+import org.argeo.api.cms.directory.CmsAuthorization;
+import org.osgi.service.useradmin.Authorization;
+
+/** Merging interface between CMS and OSGi user management APIs. */
+interface CmsOsgiAuthorization extends CmsAuthorization, Authorization {
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiGroup.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiGroup.java
new file mode 100644 (file)
index 0000000..0db368e
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.osgi.useradmin;
+
+import org.argeo.api.cms.directory.CmsGroup;
+import org.osgi.service.useradmin.Group;
+
+/** Merging interface between CMS and OSGi user management APIs. */
+interface CmsOsgiGroup extends CmsOsgiUser, CmsGroup, Group {
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiRole.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiRole.java
new file mode 100644 (file)
index 0000000..b2a18c8
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.osgi.useradmin;
+
+import org.argeo.api.cms.directory.CmsRole;
+import org.osgi.service.useradmin.Role;
+
+/** Merging interface between CMS and OSGi user management APIs. */
+interface CmsOsgiRole extends CmsRole, Role {
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiUser.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/CmsOsgiUser.java
new file mode 100644 (file)
index 0000000..14d4a9d
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.osgi.useradmin;
+
+import org.argeo.api.cms.directory.CmsUser;
+import org.osgi.service.useradmin.User;
+
+/** Merging interface between CMS and OSGi user management APIs. */
+interface CmsOsgiUser extends CmsOsgiRole, CmsUser, User {
+
+}
index 03f17e61f35b1405956203a863ee158394b09442..47cf5d3782177aa61362d7dda4aa57f10fc2067b 100644 (file)
@@ -25,15 +25,15 @@ import javax.naming.ldap.Rdn;
 import javax.security.auth.Subject;
 import javax.security.auth.kerberos.KerberosTicket;
 
+import org.argeo.api.acr.ldap.LdapNameUtils;
+import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.api.cms.directory.DirectoryDigestUtils;
-import org.argeo.api.cms.directory.CmsUser;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
 import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
 import org.argeo.cms.directory.ldap.LdapDao;
 import org.argeo.cms.directory.ldap.LdapEntry;
 import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy;
-import org.argeo.cms.directory.ldap.LdapNameUtils;
 import org.argeo.cms.directory.ldap.LdifDao;
 import org.argeo.cms.runtime.DirectoryConf;
 import org.argeo.cms.util.CurrentSubject;
@@ -126,30 +126,30 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
        }
 
        @Override
-       public String getRolePath(Role role) {
+       public String getRolePath(CmsRole role) {
                return nameToRelativePath(LdapNameUtils.toLdapName(role.getName()));
        }
 
        @Override
-       public String getRoleSimpleName(Role role) {
+       public String getRoleSimpleName(CmsRole role) {
                LdapName dn = LdapNameUtils.toLdapName(role.getName());
                String name = LdapNameUtils.getLastRdnValue(dn);
                return name;
        }
 
        @Override
-       public Role getRoleByPath(String path) {
+       public CmsRole getRoleByPath(String path) {
                LdapEntry entry = doGetRole(pathToName(path));
-               if (!(entry instanceof Role)) {
+               if (!(entry instanceof CmsRole)) {
                        return null;
 //                     throw new IllegalStateException("Path must be a UserAdmin Role.");
                } else {
-                       return (Role) entry;
+                       return (CmsRole) entry;
                }
        }
 
-       protected List<Role> getAllRoles(CmsUser user) {
-               List<Role> allRoles = new ArrayList<Role>();
+       protected List<CmsOsgiRole> getAllRoles(CmsOsgiUser user) {
+               List<CmsOsgiRole> allRoles = new ArrayList<CmsOsgiRole>();
                if (user != null) {
                        collectRoles((LdapEntry) user, allRoles);
                        allRoles.add(user);
@@ -158,17 +158,17 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
                return allRoles;
        }
 
-       private void collectRoles(LdapEntry user, List<Role> allRoles) {
+       private void collectRoles(LdapEntry user, List<CmsOsgiRole> allRoles) {
                List<LdapEntry> allEntries = new ArrayList<>();
                LdapEntry entry = user;
                collectGroups(entry, allEntries);
                for (LdapEntry e : allEntries) {
-                       if (e instanceof Role)
-                               allRoles.add((Role) e);
+                       if (e instanceof CmsOsgiRole)
+                               allRoles.add((CmsOsgiRole) e);
                }
        }
 
-       private void collectAnonymousRoles(List<Role> allRoles) {
+       private void collectAnonymousRoles(List<CmsOsgiRole> allRoles) {
                // TODO gather anonymous roles
        }
 
@@ -184,23 +184,23 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
                return res.toArray(new Role[res.size()]);
        }
 
-       List<CmsUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
+       List<CmsOsgiUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
                LdapEntryWorkingCopy wc = getWorkingCopy();
 //             Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
                List<LdapEntry> searchRes = getDirectoryDao().doGetEntries(searchBase, filter, deep);
-               List<CmsUser> res = new ArrayList<>();
+               List<CmsOsgiUser> res = new ArrayList<>();
                for (LdapEntry entry : searchRes)
-                       res.add((CmsUser) entry);
+                       res.add((CmsOsgiUser) entry);
                if (wc != null) {
-                       for (Iterator<CmsUser> it = res.iterator(); it.hasNext();) {
-                               CmsUser user = (CmsUser) it.next();
+                       for (Iterator<CmsOsgiUser> it = res.iterator(); it.hasNext();) {
+                               CmsOsgiUser user = it.next();
                                LdapName dn = LdapNameUtils.toLdapName(user.getName());
                                if (wc.getDeletedData().containsKey(dn))
                                        it.remove();
                        }
                        Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
                        for (LdapEntry ldapEntry : wc.getNewData().values()) {
-                               CmsUser user = (CmsUser) ldapEntry;
+                               CmsOsgiUser user = (CmsOsgiUser) ldapEntry;
                                if (f == null || f.match(user.getProperties()))
                                        res.add(user);
                        }
@@ -213,7 +213,7 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
        @Override
        public User getUser(String key, String value) {
                // TODO check value null or empty
-               List<CmsUser> collectedUsers = new ArrayList<CmsUser>();
+               List<CmsOsgiUser> collectedUsers = new ArrayList<>();
                if (key != null) {
                        doGetUser(key, value, collectedUsers);
                } else {
@@ -229,11 +229,11 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
                return null;
        }
 
-       protected void doGetUser(String key, String value, List<CmsUser> collectedUsers) {
+       protected void doGetUser(String key, String value, List<CmsOsgiUser> collectedUsers) {
                String f = "(" + key + "=" + value + ")";
                List<LdapEntry> users = getDirectoryDao().doGetEntries(getBaseDn(), f, true);
                for (LdapEntry entry : users)
-                       collectedUsers.add((CmsUser) entry);
+                       collectedUsers.add((CmsOsgiUser) entry);
        }
 
        @Override
@@ -242,9 +242,9 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
                        return new LdifAuthorization(user, getAllRoles(null));
                }
                LdapName userName = toLdapName(user.getName());
-               if (isExternal(userName) && user instanceof LdapEntry) {
-                       List<Role> allRoles = new ArrayList<Role>();
-                       collectRoles((LdapEntry) user, allRoles);
+               if (isExternal(userName) && user instanceof LdapEntry ldapEntry) {
+                       List<CmsOsgiRole> allRoles = new ArrayList<>();
+                       collectRoles(ldapEntry, allRoles);
                        return new LdifAuthorization(user, allRoles);
                } else {
 
@@ -263,8 +263,8 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
                                return getAuthorizationFromScoped(scopedUserAdmin, user);
                        }
 
-                       if (user instanceof CmsUser) {
-                               return new LdifAuthorization(user, getAllRoles((CmsUser) user));
+                       if (user instanceof CmsOsgiUser u) {
+                               return new LdifAuthorization(user, getAllRoles(u));
                        } else {
                                // bind with authenticating user
                                DirectoryUserAdmin scopedUserAdmin = scope(user).orElseThrow();
@@ -275,7 +275,7 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
 
        private Authorization getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin, User user) {
                try {
-                       CmsUser directoryUser = (CmsUser) scopedUserAdmin.getRole(user.getName());
+                       CmsOsgiUser directoryUser = (CmsOsgiUser) scopedUserAdmin.getRole(user.getName());
                        if (directoryUser == null)
                                throw new IllegalStateException("No scoped user found for " + user);
                        LdifAuthorization authorization = new LdifAuthorization(directoryUser,
@@ -348,7 +348,7 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
         * HIERARCHY
         */
        @Override
-       public HierarchyUnit getHierarchyUnit(Role role) {
+       public HierarchyUnit getHierarchyUnit(CmsRole role) {
                LdapName dn = LdapNameUtils.toLdapName(role.getName());
                LdapName huDn = LdapNameUtils.getParent(dn);
                HierarchyUnit hierarchyUnit = getDirectoryDao().doGetHierarchyUnit(huDn);
@@ -358,7 +358,7 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm
        }
 
        @Override
-       public Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
+       public Iterable<? extends CmsRole> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
                LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getBase());
                try {
                        return getRoles(dn, filter, deep);
index a54050bc65bdca36ce8d2298364eebe77c433b61..59e5df93110b3327ab1b413fb8bc8cd51429b6d8 100644 (file)
@@ -7,16 +7,15 @@ import java.util.List;
 
 import org.argeo.api.acr.ldap.LdapAttr;
 import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 
 /** Basic authorization. */
-class LdifAuthorization implements Authorization {
+class LdifAuthorization implements CmsOsgiAuthorization {
        private final String name;
        private final String displayName;
        private final List<String> allRoles;
 
-       public LdifAuthorization(User user, List<Role> allRoles) {
+       public LdifAuthorization(User user, List<CmsOsgiRole> allRoles) {
                if (user == null) {
                        this.name = null;
                        this.displayName = "anonymous";
index 99aca1f2f4ea20d2d7460464068dc72d79f3f72f..fd665149926c2287839e45d5b4638bfd3ca4f99d 100644 (file)
@@ -1,18 +1,18 @@
 package org.argeo.cms.osgi.useradmin;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.naming.InvalidNameException;
 import javax.naming.directory.Attribute;
 import javax.naming.ldap.LdapName;
 
-import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
 import org.osgi.service.useradmin.Role;
 
 /** Directory group implementation */
-class LdifGroup extends LdifUser implements CmsGroup {
+class LdifGroup extends LdifUser implements CmsOsgiGroup {
        private final String memberAttributeId;
 
        LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn) {
@@ -69,9 +69,9 @@ class LdifGroup extends LdifUser implements CmsGroup {
 
        @Override
        public Role[] getMembers() {
-               List<Role> directMembers = new ArrayList<Role>();
+               Set<CmsOsgiRole> directMembers = new HashSet<>();
                for (LdapName ldapName : getReferences(memberAttributeId)) {
-                       Role role = findRole(ldapName);
+                       CmsOsgiRole role = findRole(ldapName);
                        if (role == null) {
                                throw new IllegalStateException("Role " + ldapName + " not found.");
                        }
@@ -80,18 +80,35 @@ class LdifGroup extends LdifUser implements CmsGroup {
                return directMembers.toArray(new Role[directMembers.size()]);
        }
 
+       @Override
+       public Set<? extends CmsRole> getDirectMembers() {
+               return doGetDirectMembers();
+       }
+
+       protected Set<CmsOsgiRole> doGetDirectMembers() {
+               Set<CmsOsgiRole> directMembers = new HashSet<>();
+               for (LdapName ldapName : getReferences(memberAttributeId)) {
+                       CmsOsgiRole role = findRole(ldapName);
+                       if (role == null) {
+                               throw new IllegalStateException("Role " + ldapName + " not found.");
+                       }
+                       directMembers.add(role);
+               }
+               return directMembers;
+       }
+
        /**
         * Whether a role with this name can be found from this context.
         * 
         * @return The related {@link Role} or <code>null</code>.
         */
-       protected Role findRole(LdapName ldapName) {
+       protected CmsOsgiRole findRole(LdapName ldapName) {
                Role role = getUserAdmin().getRole(ldapName.toString());
                if (role == null) {
                        if (getUserAdmin().getExternalRoles() != null)
                                role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
                }
-               return role;
+               return (CmsOsgiRole) role;
        }
 
 //     @Override
index e48869a0113cc149f4bb59d48414457f5fa9bbc6..de383bde8a4f46a726f1ed2a1906f60047c434e4 100644 (file)
@@ -2,12 +2,14 @@ package org.argeo.cms.osgi.useradmin;
 
 import javax.naming.ldap.LdapName;
 
-import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
 import org.argeo.cms.directory.ldap.DefaultLdapEntry;
+import org.argeo.cms.util.LangUtils;
 
 /** Directory user implementation */
-class LdifUser extends DefaultLdapEntry implements CmsUser {
+class LdifUser extends DefaultLdapEntry implements CmsOsgiUser {
        LdifUser(AbstractLdapDirectory userAdmin, LdapName dn) {
                super(userAdmin, dn);
        }
@@ -22,4 +24,15 @@ class LdifUser extends DefaultLdapEntry implements CmsUser {
                return USER;
        }
 
+       public String getDisplayName() {
+               String dName = getPropertyAsString(LdapAttr.displayName);
+               if (LangUtils.isEmpty(dName))
+                       dName = getPropertyAsString(LdapAttr.cn);
+               if (LangUtils.isEmpty(dName))
+                       dName = getPropertyAsString(LdapAttr.uid);
+               if (LangUtils.isEmpty(dName))
+                       dName = UserAdminUtils.getUserLocalId(getName());
+               return dName;
+       }
+
 }
index 76775fed8060f912bed6a725df629113fc8f211e..1b127c89dc210f53a373b200806c9438f735e905 100644 (file)
@@ -16,8 +16,9 @@ import org.argeo.api.cms.transaction.WorkTransaction;
 import org.argeo.api.register.Component;
 import org.argeo.api.register.ComponentRegister;
 import org.argeo.api.register.SimpleRegister;
+import org.argeo.api.uuid.ConcurrentUuidFactory;
+import org.argeo.api.uuid.NodeIdSupplier;
 import org.argeo.api.uuid.UuidFactory;
-import org.argeo.cms.acr.CmsUuidFactory;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.internal.runtime.CmsDeploymentImpl;
 import org.argeo.cms.internal.runtime.CmsStateImpl;
@@ -36,19 +37,20 @@ public class StaticCms {
        private CompletableFuture<Void> stopped = new CompletableFuture<Void>();
 
        public void start() {
-               // UID factory
-               CmsUuidFactory uuidFactory = new CmsUuidFactory();
-               Component<CmsUuidFactory> uuidFactoryC = new Component.Builder<>(uuidFactory) //
-                               .addType(UuidFactory.class) //
-                               .build(register);
-
                // CMS State
                CmsStateImpl cmsState = new CmsStateImpl();
                Component<CmsStateImpl> cmsStateC = new Component.Builder<>(cmsState) //
                                .addType(CmsState.class) //
+                               .addType(NodeIdSupplier.class) //
                                .addActivation(cmsState::start) //
                                .addDeactivation(cmsState::stop) //
-                               .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsState::setUuidFactory, null) //
+                               .build(register);
+
+               // UID factory
+               ConcurrentUuidFactory uuidFactory = new ConcurrentUuidFactory();
+               Component<ConcurrentUuidFactory> uuidFactoryC = new Component.Builder<>(uuidFactory) //
+                               .addType(UuidFactory.class) //
+                               .addDependency(cmsStateC.getType(NodeIdSupplier.class), uuidFactory::setNodeIdSupplier, null) //
                                .build(register);
 
                // Transaction manager
index e4cc607d666c2777c94680222c5abcb4f1476421..67748ccd7121ce2cbae24c428f12b6840f55fde7 100644 (file)
@@ -13,7 +13,6 @@ import java.time.temporal.Temporal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -31,37 +30,16 @@ import javax.naming.ldap.LdapName;
 /** Utilities around Java basic features. */
 public class LangUtils {
        /*
-        * NON-API OSGi
+        * OBJECTS and STRINGS
         */
        /**
-        * Returns an array with the names of the provided classes. Useful when
-        * registering services with multiple interfaces in OSGi.
+        * Whether this {@link String} is <code>null</null>, empty, or only white
+        * spaces.
         */
-       public static String[] names(Class<?>... clzz) {
-               String[] res = new String[clzz.length];
-               for (int i = 0; i < clzz.length; i++)
-                       res[i] = clzz[i].getName();
-               return res;
+       public static boolean isEmpty(String str) {
+               return str == null || "".equals(str.strip());
        }
 
-//     /*
-//      * MAP
-//      */
-//     /**
-//      * Creates a new {@link Map} with one key-value pair. Key should not be null,
-//      * but if the value is null, it returns an empty {@link Map}.
-//      * 
-//      * @deprecated Use {@link Collections#singletonMap(Object, Object)} instead.
-//      */
-//     @Deprecated
-//     public static Map<String, Object> map(String key, Object value) {
-//             assert key != null;
-//             HashMap<String, Object> props = new HashMap<>();
-//             if (value != null)
-//                     props.put(key, value);
-//             return props;
-//     }
-
        /*
         * DICTIONARY
         */
@@ -345,6 +323,20 @@ public class LangUtils {
                return count > 1 ? count + " seconds" : count + " second";
        }
 
+       /*
+        * NON-API OSGi
+        */
+       /**
+        * Returns an array with the names of the provided classes. Useful when
+        * registering services with multiple interfaces in OSGi.
+        */
+       public static String[] names(Class<?>... clzz) {
+               String[] res = new String[clzz.length];
+               for (int i = 0; i < clzz.length; i++)
+                       res[i] = clzz[i].getName();
+               return res;
+       }
+
        /** Singleton constructor. */
        private LangUtils() {
 
index 3dbe6831bd05d53d5172b931bc3d8d6fe79534a2..c207cb8f18a04547895088d78c534ca3a2cb7565 100644 (file)
@@ -6,3 +6,7 @@ Bundle-Activator: org.argeo.init.osgi.Activator
 Import-Package: \
 org.osgi.*;version=0.0.0,\
 java.util.logging;resolution:=optional
+
+Export-Package: \
+org.argeo.api.*,\
+org.argeo.init.osgi,\
\ No newline at end of file
diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Branch.java b/org.argeo.init/src/org/argeo/api/a2/A2Branch.java
new file mode 100644 (file)
index 0000000..0b4ef36
--- /dev/null
@@ -0,0 +1,93 @@
+package org.argeo.api.a2;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.osgi.framework.Version;
+
+/**
+ * A logical linear sequence of versions of a given {@link A2Component}. This is
+ * typically a combination of major and minor version, indicating backward
+ * compatibility.
+ */
+public class A2Branch implements Comparable<A2Branch> {
+       private final static Logger logger = System.getLogger(A2Branch.class.getName());
+
+       private final A2Component component;
+       private final String id;
+
+       final SortedMap<Version, A2Module> modules = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       public A2Branch(A2Component component, String id) {
+               this.component = component;
+               this.id = id;
+               component.branches.put(id, this);
+       }
+
+       public Iterable<A2Module> listModules(Object filter) {
+               return modules.values();
+       }
+
+       A2Module getOrAddModule(Version version, Object locator) {
+               if (modules.containsKey(version)) {
+                       A2Module res = modules.get(version);
+                       if (!res.getLocator().equals(locator)) {
+                               logger.log(Level.TRACE,
+                                               () -> "Inconsistent locator " + locator + " (registered: " + res.getLocator() + ")");
+                       }
+                       return res;
+               } else
+                       return new A2Module(this, version, locator);
+       }
+
+       public A2Module last() {
+               return modules.get(modules.lastKey());
+       }
+
+       public A2Module first() {
+               return modules.get(modules.firstKey());
+       }
+
+       public A2Component getComponent() {
+               return component;
+       }
+
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public int compareTo(A2Branch o) {
+               return id.compareTo(id);
+       }
+
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Branch) {
+                       A2Branch o = (A2Branch) obj;
+                       return component.equals(o.component) && id.equals(o.id);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return getCoordinates();
+       }
+
+       public String getCoordinates() {
+               return component + ":" + id;
+       }
+
+       static String versionToBranchId(Version version) {
+               return version.getMajor() + "." + version.getMinor();
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Component.java b/org.argeo.init/src/org/argeo/api/a2/A2Component.java
new file mode 100644 (file)
index 0000000..d8b7512
--- /dev/null
@@ -0,0 +1,105 @@
+package org.argeo.api.a2;
+
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.osgi.framework.Version;
+
+/**
+ * The logical name of a software package. In OSGi's case this is
+ * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
+ * id.
+ */
+public class A2Component implements Comparable<A2Component> {
+       private final A2Contribution contribution;
+       private final String id;
+
+       final SortedMap<String, A2Branch> branches = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       public A2Component(A2Contribution contribution, String id) {
+               this.contribution = contribution;
+               this.id = id;
+               contribution.components.put(id, this);
+       }
+
+       public Iterable<A2Branch> listBranches(Object filter) {
+               return branches.values();
+       }
+
+       A2Branch getOrAddBranch(String branchId) {
+               if (!branches.containsKey(branchId)) {
+                       A2Branch a2Branch = new A2Branch(this, branchId);
+                       branches.put(branchId, a2Branch);
+               }
+               return branches.get(branchId);
+       }
+
+       A2Module getOrAddModule(Version version, Object locator) {
+               A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version));
+               A2Module module = branch.getOrAddModule(version, locator);
+               return module;
+       }
+
+       public A2Branch last() {
+               return branches.get(branches.lastKey());
+       }
+
+       public A2Contribution getContribution() {
+               return contribution;
+       }
+
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public int compareTo(A2Component o) {
+               return id.compareTo(o.id);
+       }
+
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Component) {
+                       A2Component o = (A2Component) obj;
+                       return contribution.equals(o.contribution) && id.equals(o.id);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return contribution.getId() + ":" + id;
+       }
+
+       void asTree(String prefix, StringBuffer buf) {
+               if (prefix == null)
+                       prefix = "";
+               A2Branch lastBranch = last();
+               SortedMap<String, A2Branch> displayMap = new TreeMap<>(Collections.reverseOrder());
+               displayMap.putAll(branches);
+               for (String branchId : displayMap.keySet()) {
+                       A2Branch branch = displayMap.get(branchId);
+                       if (!lastBranch.equals(branch)) {
+                               buf.append('\n');
+                               buf.append(prefix);
+                       } else {
+                               buf.append(" -");
+                       }
+                       buf.append(prefix);
+                       buf.append(branchId);
+                       A2Module first = branch.first();
+                       A2Module last = branch.last();
+                       buf.append(" (").append(last.getVersion());
+                       if (!first.equals(last))
+                               buf.append(" ... ").append(first.getVersion());
+                       buf.append(')');
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/api/a2/A2Contribution.java
new file mode 100644 (file)
index 0000000..a5ec896
--- /dev/null
@@ -0,0 +1,155 @@
+package org.argeo.api.a2;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A category grouping a set of {@link A2Component}, typically based on the
+ * provider of these components. This is the equivalent of Maven's group Id.
+ */
+public class A2Contribution implements Comparable<A2Contribution> {
+       final static String BOOT = "boot";
+       final static String RUNTIME = "runtime";
+       final static String CLASSPATH = "classpath";
+
+       final static String DEFAULT = "default";
+       final static String LIB = "lib";
+
+       private final ProvisioningSource source;
+       private final String id;
+
+       final Map<String, A2Component> components = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       /**
+        * The contribution must be added to the source. Rather use
+        * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this
+        * contructor directly.
+        */
+       public A2Contribution(ProvisioningSource context, String id) {
+               this.source = context;
+               this.id = id;
+//             if (context != null)
+//                     context.contributions.put(id, this);
+       }
+
+       public Iterable<A2Component> listComponents(Object filter) {
+               return components.values();
+       }
+
+       A2Component getOrAddComponent(String componentId) {
+               if (components.containsKey(componentId))
+                       return components.get(componentId);
+               else
+                       return new A2Component(this, componentId);
+       }
+
+       public ProvisioningSource getSource() {
+               return source;
+       }
+
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public int compareTo(A2Contribution o) {
+               return id.compareTo(o.id);
+       }
+
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Contribution) {
+                       A2Contribution o = (A2Contribution) obj;
+                       return id.equals(o.id);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return id;
+       }
+
+       void asTree(String prefix, StringBuffer buf) {
+               if (prefix == null)
+                       prefix = "";
+               for (String componentId : components.keySet()) {
+                       buf.append(prefix);
+                       buf.append(componentId);
+                       A2Component component = components.get(componentId);
+                       component.asTree(prefix, buf);
+                       buf.append('\n');
+               }
+       }
+
+       static String localOsArchRelativePath() {
+               return Os.local().toString() + "/" + Arch.local().toString();
+       }
+
+       /** Well-known operating systems. */
+       static enum Os {
+               LINUX, WIN32, MACOSX, UNKOWN;
+
+               @Override
+               public String toString() {
+                       return name().toLowerCase();
+               }
+
+               /** The local operating system. */
+               public static Os local() {
+                       String osStr = System.getProperty("os.name").toLowerCase();
+                       if (osStr.startsWith("linux"))
+                               return LINUX;
+                       if (osStr.startsWith("win"))
+                               return WIN32;
+                       if (osStr.startsWith("mac"))
+                               return MACOSX;
+                       return UNKOWN;
+               }
+
+       }
+
+       /** Well-known processor architectures. */
+       static enum Arch {
+               X86_64, AARCH64, X86, POWERPC, UNKOWN;
+
+               @Override
+               public String toString() {
+                       return name().toLowerCase();
+               }
+
+               /** The locla processor architecture. */
+               public static Arch local() {
+                       String archStr = System.getProperty("os.arch").toLowerCase();
+                       return switch (archStr) {
+                       case "x86_64":
+                       case "amd64":
+                       case "x86-64": {
+                               yield X86_64;
+                       }
+                       case "aarch64":
+                       case "arm64": {
+                               yield AARCH64;
+                       }
+                       case "x86":
+                       case "i386":
+                       case "i686": {
+                               yield X86;
+                       }
+                       case "powerpc":
+                       case "ppc": {
+                               yield POWERPC;
+                       }
+                       default:
+                               yield UNKOWN;
+                       };
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Exception.java b/org.argeo.init/src/org/argeo/api/a2/A2Exception.java
new file mode 100644 (file)
index 0000000..2e609f0
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.a2;
+
+/** Unchecked A2 provisioning exception. */
+public class A2Exception extends RuntimeException {
+       private static final long serialVersionUID = 1927603558545397360L;
+
+       public A2Exception(String message, Throwable e) {
+               super(message, e);
+       }
+
+       public A2Exception(String message) {
+               super(message);
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Module.java b/org.argeo.init/src/org/argeo/api/a2/A2Module.java
new file mode 100644 (file)
index 0000000..4ad4362
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.api.a2;
+
+import org.osgi.framework.Version;
+
+/**
+ * An identified software package. In OSGi's case this is the combination of
+ * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
+ * equivalent of the full coordinates of a Maven artifact version.
+ */
+public class A2Module implements Comparable<A2Module> {
+       private final A2Branch branch;
+       private final Version version;
+       private final Object locator;
+
+       public A2Module(A2Branch branch, Version version, Object locator) {
+               this.branch = branch;
+               this.version = version;
+               this.locator = locator;
+               branch.modules.put(version, this);
+       }
+
+       public A2Branch getBranch() {
+               return branch;
+       }
+
+       public Version getVersion() {
+               return version;
+       }
+
+       Object getLocator() {
+               return locator;
+       }
+
+       @Override
+       public int compareTo(A2Module o) {
+               return version.compareTo(o.version);
+       }
+
+       @Override
+       public int hashCode() {
+               return version.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Module) {
+                       A2Module o = (A2Module) obj;
+                       return branch.equals(o.branch) && version.equals(o.version);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return getCoordinates();
+       }
+
+       public String getCoordinates() {
+               return branch.getComponent() + ":" + version;
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/A2Source.java b/org.argeo.init/src/org/argeo/api/a2/A2Source.java
new file mode 100644 (file)
index 0000000..5883e9d
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.api.a2;
+
+import java.net.URI;
+
+/** A provisioning source in A2 format. */
+public interface A2Source extends ProvisioningSource {
+       /** Use standard a2 protocol, installing from source URL. */
+       final static String SCHEME_A2 = "a2";
+       /**
+        * Use equinox-specific reference: installation, which does not copy the bundle
+        * content.
+        */
+       final static String SCHEME_A2_REFERENCE = "a2+reference";
+       final static String DEFAULT_A2_URI = SCHEME_A2 + ":///";
+       final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///";
+
+       final static String INCLUDE = "include";
+       final static String EXCLUDE = "exclude";
+
+       URI getUri();
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/api/a2/AbstractProvisioningSource.java
new file mode 100644 (file)
index 0000000..62416fe
--- /dev/null
@@ -0,0 +1,269 @@
+package org.argeo.api.a2;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+/** Where components are retrieved from. */
+public abstract class AbstractProvisioningSource implements ProvisioningSource {
+       protected final Map<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       private final boolean usingReference;
+
+       public AbstractProvisioningSource(boolean usingReference) {
+               this.usingReference = usingReference;
+       }
+
+       public Iterable<A2Contribution> listContributions(Object filter) {
+               return contributions.values();
+       }
+
+       @Override
+       public Bundle install(BundleContext bc, A2Module module) {
+               try {
+                       Object locator = module.getLocator();
+                       if (usingReference && locator instanceof Path locatorPath) {
+                               String referenceUrl = "reference:file:" + locatorPath.toString();
+                               Bundle bundle = bc.installBundle(referenceUrl);
+                               return bundle;
+                       } else {
+                               Path locatorPath = (Path) locator;
+                               Path pathToUse;
+                               boolean isTemp = false;
+                               if (locator instanceof Path && Files.isDirectory(locatorPath)) {
+                                       pathToUse = toTempJar(locatorPath);
+                                       isTemp = true;
+                               } else {
+                                       pathToUse = locatorPath;
+                               }
+                               Bundle bundle;
+                               try (InputStream in = newInputStream(pathToUse)) {
+                                       bundle = bc.installBundle(locatorPath.toAbsolutePath().toString(), in);
+                               }
+
+                               if (isTemp && pathToUse != null)
+                                       Files.deleteIfExists(pathToUse);
+                               return bundle;
+                       }
+               } catch (BundleException | IOException e) {
+                       throw new A2Exception("Cannot install module " + module, e);
+               }
+       }
+
+       @Override
+       public void update(Bundle bundle, A2Module module) {
+               try {
+                       Object locator = module.getLocator();
+                       if (usingReference && locator instanceof Path) {
+                               try (InputStream in = newInputStream(locator)) {
+                                       bundle.update(in);
+                               }
+                       } else {
+                               Path locatorPath = (Path) locator;
+                               Path pathToUse;
+                               boolean isTemp = false;
+                               if (locator instanceof Path && Files.isDirectory(locatorPath)) {
+                                       pathToUse = toTempJar(locatorPath);
+                                       isTemp = true;
+                               } else {
+                                       pathToUse = locatorPath;
+                               }
+                               try (InputStream in = newInputStream(pathToUse)) {
+                                       bundle.update(in);
+                               }
+                               if (isTemp && pathToUse != null)
+                                       Files.deleteIfExists(pathToUse);
+                       }
+               } catch (BundleException | IOException e) {
+                       throw new A2Exception("Cannot update module " + module, e);
+               }
+       }
+
+       @Override
+       public A2Branch findBranch(String componentId, Version version) {
+               A2Component component = findComponent(componentId);
+               if (component == null)
+                       return null;
+               String branchId = version.getMajor() + "." + version.getMinor();
+               if (!component.branches.containsKey(branchId))
+                       return null;
+               return component.branches.get(branchId);
+       }
+
+       protected A2Contribution getOrAddContribution(String contributionId) {
+               if (contributions.containsKey(contributionId))
+                       return contributions.get(contributionId);
+               else {
+                       A2Contribution contribution = new A2Contribution(this, contributionId);
+                       contributions.put(contributionId, contribution);
+                       return contribution;
+               }
+       }
+
+       protected void asTree(String prefix, StringBuffer buf) {
+               if (prefix == null)
+                       prefix = "";
+               for (String contributionId : contributions.keySet()) {
+                       buf.append(prefix);
+                       buf.append(contributionId);
+                       buf.append('\n');
+                       A2Contribution contribution = contributions.get(contributionId);
+                       contribution.asTree(prefix + " ", buf);
+               }
+       }
+
+       protected void asTree() {
+               StringBuffer buf = new StringBuffer();
+               asTree("", buf);
+               System.out.println(buf);
+       }
+
+       protected A2Component findComponent(String componentId) {
+               SortedMap<A2Contribution, A2Component> res = new TreeMap<>();
+               for (A2Contribution contribution : contributions.values()) {
+                       components: for (String componentIdKey : contribution.components.keySet()) {
+                               if (componentId.equals(componentIdKey)) {
+                                       res.put(contribution, contribution.components.get(componentIdKey));
+                                       break components;
+                               }
+                       }
+               }
+               if (res.size() == 0)
+                       return null;
+               // TODO explicit contribution priorities
+               return res.get(res.lastKey());
+
+       }
+
+       protected String[] readNameVersionFromModule(Path modulePath) {
+               Manifest manifest;
+               if (Files.isDirectory(modulePath)) {
+                       manifest = findManifest(modulePath);
+               } else {
+                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+                               manifest = in.getManifest();
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
+                       }
+               }
+               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+               int semiColIndex = symbolicName.indexOf(';');
+               if (semiColIndex >= 0)
+                       symbolicName = symbolicName.substring(0, semiColIndex);
+               return new String[] { symbolicName, versionStr };
+       }
+
+       protected String readVersionFromModule(Path modulePath) {
+               Manifest manifest;
+               if (Files.isDirectory(modulePath)) {
+                       manifest = findManifest(modulePath);
+               } else {
+                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+                               manifest = in.getManifest();
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
+                       }
+               }
+               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+               return versionStr;
+       }
+
+       protected String readSymbolicNameFromModule(Path modulePath) {
+               Manifest manifest;
+               if (Files.isDirectory(modulePath)) {
+                       manifest = findManifest(modulePath);
+               } else {
+                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+                               manifest = in.getManifest();
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
+                       }
+               }
+               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+               int semiColIndex = symbolicName.indexOf(';');
+               if (semiColIndex >= 0)
+                       symbolicName = symbolicName.substring(0, semiColIndex);
+               return symbolicName;
+       }
+
+       protected boolean isUsingReference() {
+               return usingReference;
+       }
+
+       private InputStream newInputStream(Object locator) throws IOException {
+               if (locator instanceof Path) {
+                       return Files.newInputStream((Path) locator);
+               } else if (locator instanceof URL) {
+                       return ((URL) locator).openStream();
+               } else {
+                       throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
+               }
+       }
+
+       private static Manifest findManifest(Path currentPath) {
+               Path metaInfPath = currentPath.resolve("META-INF");
+               if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) {
+                       Path manifestPath = metaInfPath.resolve("MANIFEST.MF");
+                       try {
+                               try (InputStream in = Files.newInputStream(manifestPath)) {
+                                       Manifest manifest = new Manifest(in);
+                                       return manifest;
+                               }
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + manifestPath, e);
+                       }
+               } else {
+                       Path parentPath = currentPath.getParent();
+                       if (parentPath == null)
+                               throw new A2Exception("MANIFEST.MF file not found.");
+                       return findManifest(currentPath.getParent());
+               }
+       }
+
+       private static Path toTempJar(Path dir) {
+               try {
+                       Manifest manifest = findManifest(dir);
+                       Path jarPath = Files.createTempFile("a2Source", ".jar");
+                       try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
+                               Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                               Path relPath = dir.relativize(file);
+                                               // skip MANIFEST from folder
+                                               if (relPath.toString().contentEquals("META-INF/MANIFEST.MF"))
+                                                       return FileVisitResult.CONTINUE;
+                                               zos.putNextEntry(new ZipEntry(relPath.toString()));
+                                               Files.copy(file, zos);
+                                               zos.closeEntry();
+                                               return FileVisitResult.CONTINUE;
+                                       }
+                               });
+                       }
+                       return jarPath;
+               } catch (IOException e) {
+                       throw new A2Exception("Cannot install OSGi bundle from " + dir, e);
+               }
+
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/api/a2/ClasspathSource.java
new file mode 100644 (file)
index 0000000..b702d35
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.api.a2;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+import org.osgi.framework.Version;
+
+/**
+ * A provisioning source based on the linear classpath with which the JVM has
+ * been started.
+ */
+public class ClasspathSource extends AbstractProvisioningSource {
+       private final static Logger logger = System.getLogger(ClasspathSource.class.getName());
+
+       public ClasspathSource() {
+               super(true);
+       }
+
+       void load() throws IOException {
+               A2Contribution classpathContribution = getOrAddContribution(A2Contribution.CLASSPATH);
+               List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
+               parts: for (String part : classpath) {
+                       Path file = Paths.get(part);
+                       Version version;
+                       try {
+                               version = new Version(readVersionFromModule(file));
+                       } catch (Exception e) {
+                               // ignore non OSGi
+                               continue parts;
+                       }
+                       String moduleName = readSymbolicNameFromModule(file);
+                       A2Component component = classpathContribution.getOrAddComponent(moduleName);
+                       A2Module module = component.getOrAddModule(version, file);
+                       logger.log(Level.TRACE, () -> "Registered " + module);
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/api/a2/FsA2Source.java
new file mode 100644 (file)
index 0000000..e63a6f1
--- /dev/null
@@ -0,0 +1,173 @@
+package org.argeo.api.a2;
+
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+
+import org.osgi.framework.Version;
+
+/** A file system {@link AbstractProvisioningSource} in A2 format. */
+public class FsA2Source extends AbstractProvisioningSource implements A2Source {
+       private final static Logger logger = System.getLogger(FsA2Source.class.getName());
+
+       private final Path base;
+       private final Map<String, String> variantsXOr;
+
+       private final List<String> includes;
+       private final List<String> excludes;
+
+       public FsA2Source(Path base, Map<String, String> variantsXOr, boolean usingReference, List<String> includes,
+                       List<String> excludes) {
+               super(usingReference);
+               this.base = base;
+               this.variantsXOr = new HashMap<>(variantsXOr);
+               this.includes = includes;
+               this.excludes = excludes;
+       }
+
+       void load() throws IOException {
+               SortedMap<Path, A2Contribution> contributions = new TreeMap<>();
+
+               DirectoryStream<Path> contributionPaths = Files.newDirectoryStream(base);
+               contributions: for (Path contributionPath : contributionPaths) {
+                       if (Files.isDirectory(contributionPath)) {
+                               String contributionId = contributionPath.getFileName().toString();
+                               if (A2Contribution.BOOT.equals(contributionId))// skip boot
+                                       continue contributions;
+                               if (contributionId.contains(".")) {
+                                       A2Contribution contribution = getOrAddContribution(contributionId);
+                                       contributions.put(contributionPath, contribution);
+                               } else {// variants
+                                       Path variantPath = null;
+                                       // is it an explicit variant?
+                                       String variant = variantsXOr.get(contributionPath.getFileName().toString());
+                                       if (variant != null) {
+                                               variantPath = contributionPath.resolve(variant);
+                                       }
+
+                                       // is there a default variant?
+                                       if (variantPath == null) {
+                                               Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT);
+                                               if (Files.exists(defaultPath)) {
+                                                       variantPath = defaultPath;
+                                               }
+                                       }
+
+                                       if (variantPath == null)
+                                               continue contributions;
+
+                                       // a variant was found, let's collect its contributions (also common ones in its
+                                       // parent)
+                                       if (Files.exists(variantPath.getParent())) {
+                                               for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) {
+                                                       String variantContributionId = variantContributionPath.getFileName().toString();
+                                                       if (variantContributionId.contains(".")) {
+                                                               A2Contribution contribution = getOrAddContribution(variantContributionId);
+                                                               contributions.put(variantContributionPath, contribution);
+                                                       }
+                                               }
+                                       }
+                                       if (Files.exists(variantPath)) {
+                                               for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) {
+                                                       String variantContributionId = variantContributionPath.getFileName().toString();
+                                                       if (variantContributionId.contains(".")) {
+                                                               A2Contribution contribution = getOrAddContribution(variantContributionId);
+                                                               contributions.put(variantContributionPath, contribution);
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               contributions: for (Path contributionPath : contributions.keySet()) {
+                       String contributionId = contributionPath.getFileName().toString();
+                       if (includes != null && !includes.contains(contributionId))
+                               continue contributions;
+                       if (excludes != null && excludes.contains(contributionId))
+                               continue contributions;
+                       A2Contribution contribution = getOrAddContribution(contributionId);
+                       DirectoryStream<Path> modulePaths = Files.newDirectoryStream(contributionPath);
+                       modules: for (Path modulePath : modulePaths) {
+                               if (!Files.isDirectory(modulePath)) {
+                                       // OsgiBootUtils.debug("Registering " + modulePath);
+                                       String moduleFileName = modulePath.getFileName().toString();
+                                       int lastDot = moduleFileName.lastIndexOf('.');
+                                       String ext = moduleFileName.substring(lastDot + 1);
+                                       if (!"jar".equals(ext))
+                                               continue modules;
+                                       Version version;
+                                       // TODO optimise? check attributes?
+                                       String[] nameVersion = readNameVersionFromModule(modulePath);
+                                       String componentName = nameVersion[0];
+                                       String versionStr = nameVersion[1];
+                                       if (versionStr != null) {
+                                               version = new Version(versionStr);
+                                       } else {
+                                               logger.log(Level.TRACE, () -> "Ignore " + modulePath + " since version cannot be found");
+                                               continue modules;
+                                       }
+//                                     }
+                                       A2Component component = contribution.getOrAddComponent(componentName);
+                                       A2Module module = component.getOrAddModule(version, modulePath);
+                                       logger.log(Level.TRACE, () -> "Registered " + module);
+                               }
+                       }
+               }
+
+       }
+
+       @Override
+       public URI getUri() {
+               URI baseUri = base.toUri();
+               try {
+                       if (baseUri.getScheme().equals("file")) {
+                               String queryPart = "";
+                               if (!getVariantsXOr().isEmpty()) {
+                                       StringJoiner sj = new StringJoiner("&");
+                                       for (String key : getVariantsXOr().keySet()) {
+                                               sj.add(key + "=" + getVariantsXOr().get(key));
+                                       }
+                                       queryPart = sj.toString();
+                               }
+                               return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart,
+                                               null);
+                       } else {
+                               throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme());
+                       }
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot build URI from " + baseUri, e);
+               }
+       }
+
+       protected Map<String, String> getVariantsXOr() {
+               return variantsXOr;
+       }
+
+//     public static void main(String[] args) {
+//             if (args.length == 0)
+//                     throw new IllegalArgumentException("Usage: <path to A2 base>");
+//             try {
+//                     Map<String, String> xOr = new HashMap<>();
+//                     xOr.put("osgi", "equinox");
+//                     xOr.put("swt", "rap");
+//                     FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
+//                     context.load();
+//                     context.asTree();
+//             } catch (Exception e) {
+//                     e.printStackTrace();
+//             }
+//     }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/api/a2/FsM2Source.java
new file mode 100644 (file)
index 0000000..7cf2a04
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.api.a2;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.osgi.framework.Version;
+
+/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */
+public class FsM2Source extends AbstractProvisioningSource {
+       private final static Logger logger = System.getLogger(FsM2Source.class.getName());
+
+       private final Path base;
+
+       public FsM2Source(Path base) {
+               super(false);
+               this.base = base;
+       }
+
+       void load() throws IOException {
+               Files.walkFileTree(base, new ArtifactFileVisitor());
+       }
+
+       class ArtifactFileVisitor extends SimpleFileVisitor<Path> {
+
+               @Override
+               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                       // OsgiBootUtils.debug("Processing " + file);
+                       if (file.toString().endsWith(".jar")) {
+                               Version version;
+                               try {
+                                       version = new Version(readVersionFromModule(file));
+                               } catch (Exception e) {
+                                       // ignore non OSGi
+                                       return FileVisitResult.CONTINUE;
+                               }
+                               String moduleName = readSymbolicNameFromModule(file);
+                               Path groupPath = file.getParent().getParent().getParent();
+                               Path relGroupPath = base.relativize(groupPath);
+                               String contributionName = relGroupPath.toString().replace(File.separatorChar, '.');
+                               A2Contribution contribution = getOrAddContribution(contributionName);
+                               A2Component component = contribution.getOrAddComponent(moduleName);
+                               A2Module module = component.getOrAddModule(version, file);
+                               logger.log(Level.TRACE, () -> "Registered " + module);
+                       }
+                       return super.visitFile(file, attrs);
+               }
+
+       }
+
+       public static void main(String[] args) {
+               try {
+                       FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository"));
+                       context.load();
+                       context.asTree();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/api/a2/OsgiContext.java
new file mode 100644 (file)
index 0000000..4ec186b
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.api.a2;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+
+/**
+ * A running OSGi bundle context seen as a {@link AbstractProvisioningSource}.
+ */
+class OsgiContext extends AbstractProvisioningSource {
+       private final static Logger logger = System.getLogger(OsgiContext.class.getName());
+
+       private final BundleContext bc;
+
+       private A2Contribution runtimeContribution;
+
+       public OsgiContext(BundleContext bc) {
+               super(false);
+               this.bc = bc;
+               runtimeContribution = getOrAddContribution(A2Contribution.RUNTIME);
+       }
+
+       public OsgiContext() {
+               super(false);
+               Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class);
+               if (bundle == null)
+                       throw new IllegalArgumentException(
+                                       "OSGi Boot bundle must be started or a bundle context must be specified");
+               this.bc = bundle.getBundleContext();
+       }
+
+       void load() {
+               for (Bundle bundle : bc.getBundles()) {
+                       registerBundle(bundle);
+               }
+
+       }
+
+       void registerBundle(Bundle bundle) {
+               String componentId = bundle.getSymbolicName();
+               Version version = bundle.getVersion();
+               A2Component component = runtimeContribution.getOrAddComponent(componentId);
+               A2Module module = component.getOrAddModule(version, bundle);
+               logger.log(Level.TRACE,
+                               () -> "Registered bundle module " + module + " (location id: " + bundle.getLocation() + ")");
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningManager.java
new file mode 100644 (file)
index 0000000..af22787
--- /dev/null
@@ -0,0 +1,309 @@
+package org.argeo.api.a2;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.TRACE;
+import static org.argeo.api.a2.A2Source.SCHEME_A2;
+import static org.argeo.api.a2.A2Source.SCHEME_A2_REFERENCE;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.lang.System.Logger;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+/** Loads provisioning sources into an OSGi context. */
+public class ProvisioningManager {
+       private final static Logger logger = System.getLogger(ProvisioningManager.class.getName());
+
+       private BundleContext bc;
+       private OsgiContext osgiContext;
+       private List<ProvisioningSource> sources = Collections.synchronizedList(new ArrayList<>());
+
+       public ProvisioningManager(BundleContext bc) {
+               this.bc = bc;
+               osgiContext = new OsgiContext(bc);
+               osgiContext.load();
+       }
+
+       protected void addSource(ProvisioningSource source) {
+               sources.add(source);
+       }
+
+       void installWholeSource(ProvisioningSource source) {
+               Set<Bundle> updatedBundles = new HashSet<>();
+               for (A2Contribution contribution : source.listContributions(null)) {
+                       for (A2Component component : contribution.components.values()) {
+                               A2Module module = component.last().last();
+                               Bundle bundle = installOrUpdate(module);
+                               if (bundle != null)
+                                       updatedBundles.add(bundle);
+                       }
+               }
+//             FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
+//             frameworkWiring.refreshBundles(updatedBundles);
+       }
+
+       public void registerSource(String uri) {
+               try {
+                       URI u = new URI(uri);
+
+                       // XOR
+                       Map<String, List<String>> properties = queryToMap(u);
+                       Map<String, String> xOr = new HashMap<>();
+                       List<String> includes = null;
+                       List<String> excludes = null;
+                       for (String key : properties.keySet()) {
+                               List<String> lst = properties.get(key);
+                               if (A2Source.INCLUDE.equals(key)) {
+                                       includes = new ArrayList<>(lst);
+                               } else if (A2Source.EXCLUDE.equals(key)) {
+                                       excludes = new ArrayList<>(lst);
+                               } else {
+                                       if (lst.size() != 1)
+                                               throw new IllegalArgumentException("Invalid XOR definitions in " + uri);
+                                       xOr.put(key, lst.get(0));
+                               }
+                       }
+
+                       if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) {
+                               if (u.getHost() == null || "".equals(u.getHost())) {
+                                       String baseStr = u.getPath();
+                                       if (File.separatorChar == '\\') {// MS Windows
+                                               baseStr = baseStr.substring(1).replace('/', File.separatorChar);
+                                       }
+                                       Path base = Paths.get(baseStr);
+                                       if (Files.exists(base)) {
+                                               FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()),
+                                                               includes, excludes);
+                                               source.load();
+                                               addSource(source);
+                                               logger.log(DEBUG, () -> "Registered " + uri + " as source");
+
+                                               // OS specific / native
+                                               String localRelPath = A2Contribution.localOsArchRelativePath();
+                                               Path localLibBase = base.resolve(A2Contribution.LIB).resolve(localRelPath);
+                                               if (Files.exists(localLibBase)) {
+                                                       FsA2Source libSource = new FsA2Source(localLibBase, xOr,
+                                                                       SCHEME_A2_REFERENCE.equals(u.getScheme()), includes, excludes);
+                                                       libSource.load();
+                                                       addSource(libSource);
+                                                       logger.log(DEBUG,
+                                                                       () -> "Registered OS-specific base " + localLibBase + " for source " + uri);
+                                               }
+                                       } else {
+                                               logger.log(TRACE, () -> "Source " + base + " does not exist, ignoring.");
+                                       }
+                               } else {
+                                       throw new UnsupportedOperationException(
+                                                       "Remote installation is not yet supported, cannot add source " + u);
+                               }
+                       } else {
+                               throw new IllegalArgumentException("Unkown scheme: for source " + u);
+                       }
+               } catch (Exception e) {
+                       throw new A2Exception("Cannot add source " + uri, e);
+               }
+       }
+
+       public boolean registerDefaultSource() {
+               String frameworkLocation = bc.getProperty("osgi.framework");
+               try {
+                       URI frameworkLocationUri = new URI(frameworkLocation);
+                       if ("file".equals(frameworkLocationUri.getScheme())) {
+                               Path frameworkPath = Paths.get(frameworkLocationUri);
+                               if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) {
+                                       Path base = frameworkPath.getParent().getParent();
+                                       String baseStr = base.toString();
+                                       if (File.separatorChar == '\\')// MS Windows
+                                               baseStr = '/' + baseStr.replace(File.separatorChar, '/');
+                                       URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null);
+                                       registerSource(baseUri.toString());
+                                       logger.log(TRACE, () -> "Default source from framework location " + frameworkLocation);
+                                       return true;
+                               }
+                       }
+               } catch (Exception e) {
+                       logger.log(ERROR, "Cannot register default source based on framework location " + frameworkLocation, e);
+               }
+               return false;
+       }
+
+       public void install(String spec) {
+               if (spec == null) {
+                       for (ProvisioningSource source : sources) {
+                               installWholeSource(source);
+                       }
+               }
+       }
+
+       /** @return the new/updated bundle, or null if nothing was done. */
+       protected Bundle installOrUpdate(A2Module module) {
+               try {
+                       ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource();
+                       Version moduleVersion = module.getVersion();
+                       A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion);
+                       if (osgiBranch == null) {
+                               Bundle bundle = moduleSource.install(bc, module);
+                               // TODO make it more dynamic, based on OSGi APIs
+                               osgiContext.registerBundle(bundle);
+//                             if (OsgiBootUtils.isDebug())
+//                                     OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
+                               return bundle;
+                       } else {
+                               A2Module lastOsgiModule = osgiBranch.last();
+                               int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
+                               if (compare >= 0) {// update (also if same version)
+                                       Bundle bundle = (Bundle) lastOsgiModule.getLocator();
+                                       if (bundle.getBundleId() == 0)// ignore framework bundle
+                                               return null;
+                                       moduleSource.update(bundle, module);
+                                       // TODO make it more dynamic, based on OSGi APIs
+                                       // TODO remove old module? Or keep update history?
+                                       osgiContext.registerBundle(bundle);
+                                       if (compare == 0)
+                                               logger.log(TRACE,
+                                                               () -> "Updated bundle " + bundle.getLocation() + " to same version " + moduleVersion);
+                                       else
+                                               logger.log(INFO, "Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
+                                       return bundle;
+                               } else {
+                                       logger.log(TRACE,
+                                                       () -> "Did not install as bundle module " + module + " since a module with higher version "
+                                                                       + lastOsgiModule.getVersion() + " is already installed for branch " + osgiBranch);
+                               }
+                       }
+               } catch (Exception e) {
+                       logger.log(ERROR, "Could not install module " + module + ": " + e.getMessage(), e);
+               }
+               return null;
+       }
+
+       public Collection<Bundle> update() {
+               boolean fragmentsUpdated = false;
+               Set<Bundle> updatedBundles = new HashSet<>();
+               bundles: for (Bundle bundle : bc.getBundles()) {
+                       for (ProvisioningSource source : sources) {
+                               String componentId = bundle.getSymbolicName();
+                               Version version = bundle.getVersion();
+                               A2Branch branch = source.findBranch(componentId, version);
+                               if (branch == null)
+                                       continue bundles;
+                               A2Module module = branch.last();
+                               Version moduleVersion = module.getVersion();
+                               int compare = moduleVersion.compareTo(version);
+                               if (compare > 0) {// update
+                                       try {
+                                               source.update(bundle, module);
+//                                             bundle.update(in);
+                                               String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST);
+                                               if (fragmentHost != null)
+                                                       fragmentsUpdated = true;
+                                               logger.log(INFO, "Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
+                                               updatedBundles.add(bundle);
+                                       } catch (Exception e) {
+                                               logger.log(ERROR, "Cannot update with module " + module, e);
+                                       }
+                               }
+                       }
+               }
+               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
+               if (fragmentsUpdated)// refresh all
+                       frameworkWiring.refreshBundles(null);
+               else
+                       frameworkWiring.refreshBundles(updatedBundles);
+               return updatedBundles;
+       }
+
+       private static Map<String, List<String>> queryToMap(URI uri) {
+               return queryToMap(uri.getQuery());
+       }
+
+       private static Map<String, List<String>> queryToMap(String queryPart) {
+               try {
+                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+                       if (queryPart == null)
+                               return query_pairs;
+                       final String[] pairs = queryPart.split("&");
+                       for (String pair : pairs) {
+                               final int idx = pair.indexOf("=");
+                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+                                               : pair;
+                               if (!query_pairs.containsKey(key)) {
+                                       query_pairs.put(key, new LinkedList<String>());
+                               }
+                               final String value = idx > 0 && pair.length() > idx + 1
+                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
+                                               : null;
+                               query_pairs.get(key).add(value);
+                       }
+                       return query_pairs;
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
+               }
+       }
+
+//     public static void main(String[] args) {
+//             if (args.length == 0)
+//                     throw new IllegalArgumentException("Usage: <path to A2 base>");
+//             Map<String, String> configuration = new HashMap<>();
+//             configuration.put("osgi.console", "2323");
+//             configuration.put("org.osgi.framework.bootdelegation",
+//                             "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs");
+//             Framework framework = OsgiBootUtils.launch(configuration);
+//             try {
+//                     ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
+//                     Map<String, String> xOr = new HashMap<>();
+//                     xOr.put("osgi", "equinox");
+//                     xOr.put("swt", "rap");
+//                     FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
+//                     context.load();
+//                     pm.addSource(context);
+//                     if (framework.getBundleContext().getBundles().length == 1) {// initial
+//                             pm.install(null);
+//                     } else {
+//                             pm.update();
+//                     }
+//
+//                     Thread.sleep(2000);
+//
+//                     Bundle[] bundles = framework.getBundleContext().getBundles();
+//                     Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName()));
+//                     for (Bundle b : bundles)
+//                             if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE)
+//                                     System.out.println(b.getSymbolicName() + " " + b.getVersion());
+//                             else
+//                                     System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")");
+//             } catch (Exception e) {
+//                     e.printStackTrace();
+//             } finally {
+//                     try {
+//                             framework.stop();
+//                     } catch (Exception e) {
+//                             e.printStackTrace();
+//                     }
+//             }
+//     }
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java b/org.argeo.init/src/org/argeo/api/a2/ProvisioningSource.java
new file mode 100644 (file)
index 0000000..ddba2a9
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.api.a2;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+
+/** Where components are retrieved from. */
+public interface ProvisioningSource {
+       /** List all contributions of this source. */
+       Iterable<A2Contribution> listContributions(Object filter);
+
+       /** Install a module in the OSGi runtime. */
+       Bundle install(BundleContext bc, A2Module module);
+
+       /** Update a module in the OSGi runtime. */
+       void update(Bundle bundle, A2Module module);
+
+       /** Finds the {@link A2Branch} related to this component and version. */
+       A2Branch findBranch(String componentId, Version version);
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/a2/package-info.java b/org.argeo.init/src/org/argeo/api/a2/package-info.java
new file mode 100644 (file)
index 0000000..6a8bf71
--- /dev/null
@@ -0,0 +1,2 @@
+/** A2 OSGi repository format. */
+package org.argeo.api.a2;
\ No newline at end of file
diff --git a/org.argeo.init/src/org/argeo/api/init/InitConstants.java b/org.argeo.init/src/org/argeo/api/init/InitConstants.java
new file mode 100644 (file)
index 0000000..3c558f8
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.api.init;
+
+/** Supported init constants. */
+public interface InitConstants {
+
+       String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources";
+       String PROP_ARGEO_OSGI_START = "argeo.osgi.start";
+       String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area";
+       String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
+       String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area";
+       String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel";
+       /** UUID of the parent framework. Marks a nested runtime. */
+       String PROP_ARGEO_OSGI_PARENT_UUID = "argeo.osgi.parent.uuid";
+
+       // OSGi standard properties
+       String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel";
+       String PROP_OSGI_STARTLEVEL = "osgi.startLevel";
+       String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties";
+
+       // Symbolic names
+       String SYMBOLIC_NAME_INIT = "org.argeo.init";
+       String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
+
+}
diff --git a/org.argeo.init/src/org/argeo/api/init/RuntimeContext.java b/org.argeo.init/src/org/argeo/api/init/RuntimeContext.java
new file mode 100644 (file)
index 0000000..9f78d13
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.api.init;
+
+/** A runtime context with a life cycle. */
+public interface RuntimeContext extends Runnable {
+       /** Wait until this runtime context has closed. */
+       void waitForStop(long timeout) throws InterruptedException;
+
+       /** Close this runtime context. */
+       void close() throws Exception;
+}
diff --git a/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java b/org.argeo.init/src/org/argeo/api/init/RuntimeManager.java
new file mode 100644 (file)
index 0000000..cb8caed
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.api.init;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+/** Dynamically manages multiple runtimes within a single JVM. */
+public interface RuntimeManager {
+       String JVM_ARGS = "jvm.args";
+       String STATE = "state";
+       String DATA = "data";
+
+       public void startRuntime(String relPath, Consumer<Map<String, String>> configCallback);
+
+       public void closeRuntime(String relPath, boolean async);
+
+       /**
+        * Load configs recursively starting with the parent directories, until a
+        * jvm.args file is found.
+        */
+       static void loadConfig(Path dir, Map<String, String> config) {
+               try {
+                       Path jvmArgsPath = dir.resolve(RuntimeManager.JVM_ARGS);
+                       if (!Files.exists(jvmArgsPath)) {
+                               // load from parent directory
+                               loadConfig(dir.getParent(), config);
+                       }
+
+                       if (Files.exists(dir))
+                               for (Path p : Files.newDirectoryStream(dir, "*.ini")) {
+                                       try (InputStream in = Files.newInputStream(p)) {
+                                               loadConfig(in, config);
+                                       }
+                               }
+               } catch (IOException e) {
+                       throw new UncheckedIOException("Cannot load configuration from " + dir, e);
+               }
+       }
+
+       /**
+        * Load config from a {@link Properties} formatted stream. If a property value
+        * starts with a '+' character, itis expected that the last character is a
+        * separator and it will be prepended to the existing value.
+        */
+       static void loadConfig(InputStream in, Map<String, String> config) throws IOException {
+               Properties props = new Properties();
+               props.load(in);
+               for (Object k : props.keySet()) {
+                       String key = k.toString();
+                       String value = props.getProperty(key);
+                       if (value.length() > 1 && '+' == value.charAt(0)) {
+                               String currentValue = config.get(key);
+                               if (currentValue == null || "".equals(currentValue)) {
+                                       // remove the + and the trailing separator
+                                       value = value.substring(1, value.length() - 1);
+                                       config.put(key, value);
+                               } else {
+                                       // remove the + but keep the trailing separator
+                                       value = value.substring(1);
+                                       config.put(key, value + currentValue);
+                               }
+                       } else {
+                               config.put(key, value);
+                       }
+               }
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/RuntimeContext.java b/org.argeo.init/src/org/argeo/init/RuntimeContext.java
deleted file mode 100644 (file)
index d83f2ca..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.argeo.init;
-
-/** A runtime context with a life cycle. */
-public interface RuntimeContext extends Runnable {
-       /** Wait until this runtime context has closed. */
-       void waitForStop(long timeout) throws InterruptedException;
-
-       /** Close this runtime context. */
-       void close() throws Exception;
-}
diff --git a/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java b/org.argeo.init/src/org/argeo/init/RuntimeManagerMain.java
new file mode 100644 (file)
index 0000000..f4ed507
--- /dev/null
@@ -0,0 +1,185 @@
+package org.argeo.init;
+
+import static org.argeo.api.init.InitConstants.SYMBOLIC_NAME_INIT;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+
+import org.argeo.api.init.InitConstants;
+import org.argeo.api.init.RuntimeContext;
+import org.argeo.api.init.RuntimeManager;
+import org.argeo.init.logging.ThinLoggerFinder;
+import org.argeo.init.osgi.OsgiBoot;
+import org.argeo.init.osgi.OsgiRuntimeContext;
+import org.argeo.internal.init.InternalState;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.launch.Framework;
+
+/**
+ * Dynamically configures and launches multiple runtimes, coordinated by a main
+ * one.
+ */
+public class RuntimeManagerMain implements RuntimeManager {
+       private final static Logger logger = System.getLogger(RuntimeManagerMain.class.getName());
+
+       private final static String ENV_STATE_DIRECTORY = "STATE_DIRECTORY";
+//     private final static String ENV_CONFIGURATION_DIRECTORY = "CONFIGURATION_DIRECTORY";
+//     private final static String ENV_CACHE_DIRECTORY = "CACHE_DIRECTORY";
+
+       private final static long RUNTIME_SHUTDOWN_TIMEOUT = 60 * 1000;
+
+       private Path baseConfigArea;
+       private Path baseWritableArea;
+       private Map<String, String> configuration = new HashMap<>();
+
+       private Map<String, OsgiRuntimeContext> runtimeContexts = new TreeMap<>();
+
+       RuntimeManagerMain(Path configArea, Path stateArea) {
+               RuntimeManager.loadConfig(configArea, configuration);
+               configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea.resolve(STATE).toUri().toString());
+               // use config area if instance area is not set
+               if (!configuration.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA))
+                       configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA, stateArea.resolve(DATA).toUri().toString());
+               this.baseConfigArea = configArea.getParent();
+               this.baseWritableArea = stateArea.getParent();
+
+               logger.log(Level.TRACE, () -> "Runtime manager configuration: " + configuration);
+
+//             System.out.println("java.library.path=" + System.getProperty("java.library.path"));
+       }
+
+       public void run() {
+               OsgiRuntimeContext managerRuntimeContext = new OsgiRuntimeContext(configuration);
+               try {
+                       managerRuntimeContext.run();
+                       InternalState.setMainRuntimeContext(managerRuntimeContext);
+
+                       // shutdown on exit
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(), "Runtime shutdown"));
+
+                       BundleContext bc = managerRuntimeContext.getFramework().getBundleContext();
+                       // uninstall init as a bundle since it will be available via OSGi system
+                       OsgiBoot.uninstallBundles(bc, SYMBOLIC_NAME_INIT);
+                       bc.registerService(RuntimeManager.class, this, new Hashtable<>(configuration));
+                       logger.log(Level.DEBUG, "Registered runtime manager");
+
+                       managerRuntimeContext.waitForStop(0);
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+
+       }
+
+       protected void shutdown() {
+               // shutdowm runtimes
+               Map<String, RuntimeContext> shutdowning = new HashMap<>(runtimeContexts);
+               for (String id : new HashSet<>(runtimeContexts.keySet())) {
+                       logger.log(Logger.Level.DEBUG, "Shutting down runtime " + id + " ...");
+                       closeRuntime(id, true);
+               }
+               for (String id : shutdowning.keySet())
+                       try {
+                               RuntimeContext runtimeContext = shutdowning.get(id);
+                               runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT);
+                       } catch (InterruptedException e) {
+                               // silent
+                       } catch (Exception e) {
+                               logger.log(Logger.Level.DEBUG, "Cannot wait for " + id + " to shutdown", e);
+                       }
+               // shutdown manager runtime
+               try {
+                       InternalState.getMainRuntimeContext().close();
+                       InternalState.getMainRuntimeContext().waitForStop(RUNTIME_SHUTDOWN_TIMEOUT);
+//                     logger.log(Logger.Level.INFO, "Argeo Init stopped with PID " + ProcessHandle.current().pid());
+                       System.out.flush();
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       Runtime.getRuntime().halt(1);
+               }
+       }
+
+       OsgiRuntimeContext loadRuntime(String relPath, Consumer<Map<String, String>> configCallback) {
+               closeRuntime(relPath, false);
+               Path writableArea = baseWritableArea.resolve(relPath);
+               Path configArea = baseConfigArea.resolve(relPath);
+               Map<String, String> config = new HashMap<>();
+               RuntimeManager.loadConfig(configArea, config);
+               config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, writableArea.resolve(STATE).toUri().toString());
+
+               if (configCallback != null)
+                       configCallback.accept(config);
+
+               // use config area if instance area is not set
+               if (!config.containsKey(InitConstants.PROP_OSGI_INSTANCE_AREA))
+                       config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, writableArea.resolve(DATA).toUri().toString());
+
+               OsgiRuntimeContext runtimeContext = new OsgiRuntimeContext(config);
+               runtimeContexts.put(relPath, runtimeContext);
+               return runtimeContext;
+       }
+
+       public void startRuntime(String relPath, Consumer<Map<String, String>> configCallback) {
+               OsgiRuntimeContext runtimeContext = loadRuntime(relPath, configCallback);
+               runtimeContext.run();
+               Framework framework = runtimeContext.getFramework();
+               if (framework != null) {// in case the framework has closed very quickly after run
+                       framework.getBundleContext().addFrameworkListener((e) -> {
+                               if (e.getType() >= FrameworkEvent.STOPPED) {
+                                       logger.log(Level.DEBUG, "Externally stopped runtime " + relPath + ". Unregistering...", e);
+                                       runtimeContexts.remove(relPath);
+                               }
+                       });
+               } else {
+                       closeRuntime(relPath, false);
+               }
+       }
+
+       public void closeRuntime(String relPath, boolean async) {
+               if (!runtimeContexts.containsKey(relPath))
+                       return;
+               RuntimeContext runtimeContext = runtimeContexts.get(relPath);
+               try {
+                       runtimeContext.close();
+                       if (!async) {
+                               runtimeContext.waitForStop(RUNTIME_SHUTDOWN_TIMEOUT);
+                               System.gc();
+                       }
+               } catch (Exception e) {
+                       logger.log(Level.ERROR, "Cannot close runtime context " + relPath, e);
+               } finally {
+                       runtimeContexts.remove(relPath);
+               }
+
+       }
+
+       public static void main(String[] args) {
+               ThinLoggerFinder.reloadConfiguration();
+               logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + ProcessHandle.current().pid());
+               Map<String, String> env = System.getenv();
+//             for (String envName : new TreeSet<>(env.keySet())) {
+//                     System.out.format("%s=%s%n", envName, env.get(envName));
+//             }
+               if (args.length < 1)
+                       throw new IllegalArgumentException("A relative configuration directory must be specified");
+               Path configArea = Paths.get(System.getProperty("user.dir"), args[0]);
+
+               // System.out.println("## Start with PID " + ProcessHandle.current().pid());
+               // System.out.println("user.dir=" + System.getProperty("user.dir"));
+
+               Path stateArea = Paths.get(env.get(ENV_STATE_DIRECTORY));
+
+               RuntimeManagerMain runtimeManager = new RuntimeManagerMain(configArea, stateArea);
+               runtimeManager.run();
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/Service.java
deleted file mode 100644 (file)
index b080a75..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.init;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.TreeMap;
-
-import org.argeo.init.logging.ThinLoggerFinder;
-import org.argeo.init.osgi.OsgiBoot;
-import org.argeo.init.osgi.OsgiRuntimeContext;
-
-/** Configure and launch an Argeo service. */
-public class Service {
-       private final static Logger logger = System.getLogger(Service.class.getName());
-
-       final static String FILE_SYSTEM_PROPERTIES = "system.properties";
-
-       public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main";
-
-       private static RuntimeContext runtimeContext = null;
-
-       private static List<Runnable> postStart = Collections.synchronizedList(new ArrayList<>());
-
-       protected Service(String[] args) {
-       }
-
-       public static void main(String[] args) {
-               final long pid = ProcessHandle.current().pid();
-               logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid);
-
-               // shutdown on exit
-               Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-                       try {
-                               if (Service.runtimeContext != null) {
-//                                     System.out.println("Argeo Init stopping with PID " + pid);
-                                       Service.runtimeContext.close();
-                                       Service.runtimeContext.waitForStop(0);
-                               }
-                       } catch (Exception e) {
-                               e.printStackTrace();
-                               Runtime.getRuntime().halt(1);
-                       }
-               }, "Runtime shutdown"));
-
-               // TODO use args as well
-               String dataArea = System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA);
-               String stateArea = System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA);
-               String configArea = System.getProperty(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA);
-
-               if (configArea != null) {
-                       Path configAreaPath = Paths.get(configArea);
-                       Path additionalSystemPropertiesPath = configAreaPath.resolve(FILE_SYSTEM_PROPERTIES);
-                       if (Files.exists(additionalSystemPropertiesPath)) {
-                               Properties properties = new Properties();
-                               try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) {
-                                       properties.load(in);
-                               } catch (IOException e) {
-                                       logger.log(Logger.Level.ERROR,
-                                                       "Cannot load additional system properties " + additionalSystemPropertiesPath, e);
-                               }
-
-                               for (Object key : properties.keySet()) {
-                                       String currentValue = System.getProperty(key.toString());
-                                       String value = properties.getProperty(key.toString());
-                                       if (currentValue != null) {
-                                               if (!Objects.equals(value, currentValue))
-                                                       logger.log(Logger.Level.WARNING, "System property " + key + " already set with value "
-                                                                       + currentValue + " instead of " + value + ". Ignoring new value.");
-                                       } else {
-                                               System.setProperty(key.toString(), value);
-                                               logger.log(Logger.Level.TRACE, () -> "Added " + key + "=" + value
-                                                               + " to system properties, from " + additionalSystemPropertiesPath.getFileName());
-                                       }
-                               }
-                               ThinLoggerFinder.reloadConfiguration();
-                       }
-               }
-
-               Map<String, String> config = new HashMap<>();
-               config.put(PROP_ARGEO_INIT_MAIN, "true");
-
-               // add OSGi system properties to the configuration
-               sysprops: for (Object key : new TreeMap<>(System.getProperties()).keySet()) {
-                       String keyStr = key.toString();
-                       switch (keyStr) {
-                       case OsgiBoot.PROP_OSGI_CONFIGURATION_AREA:
-                       case OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA:
-                       case OsgiBoot.PROP_OSGI_INSTANCE_AREA:
-                               // we should already have dealt with those
-                               continue sysprops;
-                       default:
-                       }
-
-                       if (keyStr.startsWith("osgi.") || keyStr.startsWith("org.osgi.") || keyStr.startsWith("eclipse.")
-                                       || keyStr.startsWith("org.eclipse.equinox.") || keyStr.startsWith("felix.")) {
-                               String value = System.getProperty(keyStr);
-                               config.put(keyStr, value);
-                               logger.log(Logger.Level.TRACE,
-                                               () -> "Added " + key + "=" + value + " to configuration, from system properties");
-                       }
-               }
-
-               try {
-                       try {
-                               if (stateArea != null)
-                                       config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea);
-                               if (configArea != null)
-                                       config.put(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea);
-                               if (dataArea != null)
-                                       config.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, dataArea);
-                               // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true");
-
-                               OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config);
-                               osgiRuntimeContext.run();
-                               Service.runtimeContext = osgiRuntimeContext;
-                               for (Runnable run : postStart) {
-                                       try {
-                                               run.run();
-                                       } catch (Exception e) {
-                                               logger.log(Level.ERROR, "Cannot run post start callback " + run, e);
-                                       }
-                               }
-                               Service.runtimeContext.waitForStop(0);
-                       } catch (NoClassDefFoundError noClassDefFoundE) {
-                               StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map<String, String>) config);
-                               staticRuntimeContext.run();
-                               Service.runtimeContext = staticRuntimeContext;
-                               for (Runnable run : postStart) {
-                                       try {
-                                               run.run();
-                                       } catch (Exception e) {
-                                               logger.log(Level.ERROR, "Cannot run post start callback " + run, e);
-                                       }
-                               }
-                               Service.runtimeContext.waitForStop(0);
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-               logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid);
-       }
-
-       /** The root runtime context in this JVM. */
-       public static RuntimeContext getRuntimeContext() {
-               return runtimeContext;
-       }
-
-       /** Add a post-start call back to be run after the runtime has been started. */
-       public static void addPostStart(Runnable runnable) {
-               postStart.add(runnable);
-       }
-}
diff --git a/org.argeo.init/src/org/argeo/init/ServiceMain.java b/org.argeo.init/src/org/argeo/init/ServiceMain.java
new file mode 100644 (file)
index 0000000..ca8ba3a
--- /dev/null
@@ -0,0 +1,159 @@
+package org.argeo.init;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.argeo.api.init.InitConstants;
+import org.argeo.init.logging.ThinLoggerFinder;
+import org.argeo.init.osgi.OsgiRuntimeContext;
+import org.argeo.internal.init.InternalState;
+
+/** Configures and launches a single runtime, typically as a systemd service. */
+public class ServiceMain {
+       private final static Logger logger = System.getLogger(ServiceMain.class.getName());
+
+       final static String FILE_SYSTEM_PROPERTIES = "system.properties";
+
+       public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main";
+
+//     private static RuntimeContext runtimeContext = null;
+
+       private static List<Runnable> postStart = Collections.synchronizedList(new ArrayList<>());
+
+       protected ServiceMain(String[] args) {
+       }
+
+       public static void main(String[] args) {
+               final long pid = ProcessHandle.current().pid();
+               logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid);
+
+               // shutdown on exit
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                       try {
+                               if (InternalState.getMainRuntimeContext() != null) {
+                                       InternalState.getMainRuntimeContext().close();
+                                       InternalState.getMainRuntimeContext().waitForStop(0);
+                               }
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                               Runtime.getRuntime().halt(1);
+                       }
+               }, "Runtime shutdown"));
+
+               // TODO use args as well
+               String dataArea = System.getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA);
+               String stateArea = System.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
+               String configArea = System.getProperty(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA);
+
+               if (configArea != null) {
+                       Path configAreaPath = Paths.get(configArea);
+                       Path additionalSystemPropertiesPath = configAreaPath.resolve(FILE_SYSTEM_PROPERTIES);
+                       if (Files.exists(additionalSystemPropertiesPath)) {
+                               Properties properties = new Properties();
+                               try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) {
+                                       properties.load(in);
+                               } catch (IOException e) {
+                                       logger.log(Logger.Level.ERROR,
+                                                       "Cannot load additional system properties " + additionalSystemPropertiesPath, e);
+                               }
+
+                               for (Object key : properties.keySet()) {
+                                       String currentValue = System.getProperty(key.toString());
+                                       String value = properties.getProperty(key.toString());
+                                       if (currentValue != null) {
+                                               if (!Objects.equals(value, currentValue))
+                                                       logger.log(Logger.Level.WARNING, "System property " + key + " already set with value "
+                                                                       + currentValue + " instead of " + value + ". Ignoring new value.");
+                                       } else {
+                                               System.setProperty(key.toString(), value);
+                                               logger.log(Logger.Level.TRACE, () -> "Added " + key + "=" + value
+                                                               + " to system properties, from " + additionalSystemPropertiesPath.getFileName());
+                                       }
+                               }
+                               ThinLoggerFinder.reloadConfiguration();
+                       }
+               }
+
+               Map<String, String> config = new HashMap<>();
+               config.put(PROP_ARGEO_INIT_MAIN, "true");
+
+               // add OSGi system properties to the configuration
+               sysprops: for (Object key : new TreeMap<>(System.getProperties()).keySet()) {
+                       String keyStr = key.toString();
+                       switch (keyStr) {
+                       case InitConstants.PROP_OSGI_CONFIGURATION_AREA:
+                       case InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA:
+                       case InitConstants.PROP_OSGI_INSTANCE_AREA:
+                               // we should already have dealt with those
+                               continue sysprops;
+                       default:
+                       }
+
+                       if (keyStr.startsWith("osgi.") || keyStr.startsWith("org.osgi.") || keyStr.startsWith("eclipse.")
+                                       || keyStr.startsWith("org.eclipse.equinox.") || keyStr.startsWith("felix.")) {
+                               String value = System.getProperty(keyStr);
+                               config.put(keyStr, value);
+                               logger.log(Logger.Level.TRACE,
+                                               () -> "Added " + key + "=" + value + " to configuration, from system properties");
+                       }
+               }
+
+               try {
+                       try {
+                               if (stateArea != null)
+                                       config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA, stateArea);
+                               if (configArea != null)
+                                       config.put(InitConstants.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea);
+                               if (dataArea != null)
+                                       config.put(InitConstants.PROP_OSGI_INSTANCE_AREA, dataArea);
+                               // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true");
+
+                               OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config);
+                               osgiRuntimeContext.run();
+                               InternalState.setMainRuntimeContext(osgiRuntimeContext);
+                               for (Runnable run : postStart) {
+                                       try {
+                                               run.run();
+                                       } catch (Exception e) {
+                                               logger.log(Level.ERROR, "Cannot run post start callback " + run, e);
+                                       }
+                               }
+                               InternalState.getMainRuntimeContext().waitForStop(0);
+                       } catch (NoClassDefFoundError noClassDefFoundE) {
+                               StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map<String, String>) config);
+                               staticRuntimeContext.run();
+                               InternalState.setMainRuntimeContext(staticRuntimeContext);
+                               for (Runnable run : postStart) {
+                                       try {
+                                               run.run();
+                                       } catch (Exception e) {
+                                               logger.log(Level.ERROR, "Cannot run post start callback " + run, e);
+                                       }
+                               }
+                               InternalState.getMainRuntimeContext().waitForStop(0);
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+               logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid);
+       }
+
+       /** Add a post-start call back to be run after the runtime has been started. */
+       public static void addPostStart(Runnable runnable) {
+               postStart.add(runnable);
+       }
+}
index e01e6194dd6916a377b3b8ad1afa3e9bf5fa2a02..51a968804308fe616064c2f498f0fa8972816db5 100644 (file)
@@ -2,6 +2,8 @@ package org.argeo.init;
 
 import java.util.Map;
 
+import org.argeo.api.init.RuntimeContext;
+
 public class StaticRuntimeContext implements RuntimeContext {
        private Map<String, String> config;
 
diff --git a/org.argeo.init/src/org/argeo/init/SysInitMain.java b/org.argeo.init/src/org/argeo/init/SysInitMain.java
new file mode 100644 (file)
index 0000000..8e59c24
--- /dev/null
@@ -0,0 +1,308 @@
+package org.argeo.init;
+//#! /usr/bin/java --source 17 @/usr/local/etc/freed/pid1/jvm.args
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.io.Console;
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import sun.misc.Signal;
+
+/** A minimalistic Linux init process. */
+class SysInitMain {
+       final static AtomicInteger runLevel = new AtomicInteger(-1);
+
+       private final static Logger logger = System.getLogger(SysInitMain.class.getName());
+
+       private final static List<String> initDServices = Collections.synchronizedList(new ArrayList<>());
+
+       public static void main(String... args) {
+               try {
+                       final long pid = ProcessHandle.current().pid();
+                       Signal.handle(new Signal("TERM"), (signal) -> {
+                               System.out.println("SIGTERM caught");
+                               System.exit(0);
+                       });
+                       Signal.handle(new Signal("INT"), (signal) -> {
+                               System.out.println("SIGINT caught");
+                               System.exit(0);
+                       });
+                       Signal.handle(new Signal("HUP"), (signal) -> {
+                               System.out.println("SIGHUP caught");
+                               System.exit(0);
+                       });
+
+                       boolean isSystemInit = pid == 1 || pid == 2;
+
+                       if (isSystemInit && args.length > 0 && ("1".equals(args[0]) //
+                                       || "single".equals(args[0]) //
+                                       || "emergency".equals(args[0]))) {
+                               runLevel.set(1);
+                               for (Object key : new TreeMap<>(System.getProperties()).keySet()) {
+                                       System.out.println(key + "=" + System.getProperty(key.toString()));
+                               }
+                               System.out.println("Single user mode");
+                               System.out.flush();
+                               ProcessBuilder pb = new ProcessBuilder("/bin/bash");
+                               pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+                               pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+                               pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
+                               Process singleUserShell = pb.start();
+                               singleUserShell.waitFor();
+                       } else {
+                               if (args.length == 0)
+                                       runLevel.set(5);
+                               else
+                                       runLevel.set(Integer.parseInt(args[0]));
+
+                               if (runLevel.get() == 0) {// shutting down the whole system
+                                       if (!isSystemInit) {
+                                               logger.log(INFO, "Shutting down system...");
+                                               shutdown(false);
+                                               System.exit(0);
+                                       } else {
+                                               logger.log(ERROR, "Cannot start at run level " + runLevel.get());
+                                               System.exit(1);
+                                       }
+                               } else if (runLevel.get() == 6) {// reboot the whole system
+                                       if (!isSystemInit) {
+                                               logger.log(INFO, "Rebooting the system...");
+                                               shutdown(true);
+                                       } else {
+                                               logger.log(ERROR, "Cannot start at run level " + runLevel.get());
+                                               System.exit(1);
+                                       }
+                               }
+
+                               logger.log(INFO, "FREEd Init daemon starting with pid " + pid + " after "
+                                               + ManagementFactory.getRuntimeMXBean().getUptime() + " ms");
+                               // hostname
+                               String hostname = Files.readString(Paths.get("/etc/hostname"));
+                               new ProcessBuilder("/usr/bin/hostname", hostname).start();
+                               logger.log(DEBUG, "Set hostname to " + hostname);
+                               // networking
+                               initSysctl();
+                               startInitDService("networking", true);
+//                             Thread.sleep(3000);// leave some time for network to start up
+                               if (!waitForNetwork(10 * 1000))
+                                       logger.log(ERROR, "No network available");
+
+                               // OpenSSH
+                               // TODO make it coherent with Java sshd
+                               startInitDService("ssh", true);
+
+                               // NSS services
+                               startInitDService("nslcd", false);// Note: nslcd fails to stop
+
+                               // login prompt
+                               ServiceMain.addPostStart(() -> new LoginThread().start());
+
+                               // init Argeo CMS
+                               logger.log(INFO, "FREEd Init daemon starting Argeo Init after "
+                                               + ManagementFactory.getRuntimeMXBean().getUptime() + " ms");
+                               ServiceMain.main(args);
+                       }
+               } catch (Throwable e) {
+                       logger.log(ERROR, "Unexpected exception in free-pid1 init, shutting down... ", e);
+                       System.exit(1);
+               } finally {
+                       stopInitDServices();
+               }
+       }
+
+       static void initSysctl() {
+               try {
+                       Path sysctlD = Paths.get("/etc/sysctl.d/");
+                       for (Path conf : Files.newDirectoryStream(sysctlD, "*.conf")) {
+                               try {
+                                       new ProcessBuilder("/usr/sbin/sysctl", "-p", conf.toString()).start();
+                               } catch (IOException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               } catch (IOException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       static void startInitDService(String serviceName, boolean stopOnShutdown) {
+               Path serviceInit = Paths.get("/etc/init.d/", serviceName);
+               if (Files.exists(serviceInit))
+                       try {
+                               int exitCode = new ProcessBuilder(serviceInit.toString(), "start").start().waitFor();
+                               if (exitCode != 0)
+                                       logger.log(ERROR, "Service " + serviceName + " dit not stop properly");
+                               else
+                                       logger.log(DEBUG, "Service " + serviceName + " started");
+                               if (stopOnShutdown)
+                                       initDServices.add(serviceName);
+//                                     Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+//                                             try {
+//                                                     new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor();
+//                                             } catch (IOException | InterruptedException e) {
+//                                                     e.printStackTrace();
+//                                             }
+//                                     }, "FREEd stop service " + serviceName));
+                       } catch (IOException | InterruptedException e) {
+                               e.printStackTrace();
+                       }
+               else
+                       logger.log(WARNING, "Service " + serviceName + " not found and therefore not started");
+       }
+
+       static boolean waitForNetwork(long timeout) {
+               long begin = System.currentTimeMillis();
+               long duration = 0;
+               boolean networkAvailable = false;
+               try {
+                       networkAvailable: while (!networkAvailable) {
+                               duration = System.currentTimeMillis() - begin;
+                               if (duration > timeout)
+                                       break networkAvailable;
+                               Enumeration<NetworkInterface> netInterfaces = null;
+                               try {
+                                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+                               } catch (SocketException e) {
+                                       throw new IllegalStateException("Cannot list network interfaces", e);
+                               }
+                               if (netInterfaces != null) {
+                                       while (netInterfaces.hasMoreElements()) {
+                                               NetworkInterface netInterface = netInterfaces.nextElement();
+                                               logger.log(DEBUG, "Interface:" + netInterface);
+                                               for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                                       InetAddress inetAddr = addr.getAddress();
+                                                       logger.log(DEBUG, "  addr: " + inetAddr);
+                                                       if (!inetAddr.isLoopbackAddress() && !inetAddr.isLinkLocalAddress()) {
+                                                               try {
+                                                                       if (inetAddr.isReachable((int) timeout)) {
+                                                                               networkAvailable = true;
+                                                                               duration = System.currentTimeMillis() - begin;
+                                                                               logger.log(DEBUG,
+                                                                                               "Network available after " + duration + " ms. IP: " + inetAddr);
+                                                                               break networkAvailable;
+                                                                       }
+                                                               } catch (IOException e) {
+                                                                       logger.log(ERROR, "Cannot check whether " + inetAddr + " is reachable", e);
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               } else {
+                                       throw new IllegalStateException("No network interface has been found");
+                               }
+                               try {
+                                       Thread.sleep(1000);
+                               } catch (InterruptedException e) {
+                                       // silent
+                               }
+                       }
+               } catch (Exception e) {
+                       logger.log(ERROR, "Cannot check whether network is available", e);
+               }
+               return networkAvailable;
+       }
+
+       static void shutdown(boolean reboot) {
+               try {
+                       stopInitDServices();
+                       Path sysrqP = Paths.get("/proc/sys/kernel/sysrq");
+                       Files.writeString(sysrqP, "1");
+                       Path sysrqTriggerP = Paths.get("/proc/sysrq-trigger");
+                       Files.writeString(sysrqTriggerP, "e");// send SIGTERM to all processes
+                       // Files.writeString(sysrqTriggerP, "i");// send SIGKILL to all processes
+                       Files.writeString(sysrqTriggerP, "e");// flush data to disk
+                       Files.writeString(sysrqTriggerP, "u");// unmount
+                       if (reboot)
+                               Files.writeString(sysrqTriggerP, "b");
+                       else
+                               Files.writeString(sysrqTriggerP, "o");
+               } catch (IOException e) {
+                       logger.log(ERROR, "Cannot shut down system", e);
+               }
+       }
+
+       static void stopInitDServices() {
+               for (int i = initDServices.size() - 1; i >= 0; i--) {
+                       String serviceName = initDServices.get(i);
+                       Path serviceInit = Paths.get("/etc/init.d/", serviceName);
+                       try {
+                               int exitCode = new ProcessBuilder(serviceInit.toString(), "stop").start().waitFor();
+                               if (exitCode != 0)
+                                       logger.log(ERROR, "Service " + serviceName + " dit not stop properly");
+                       } catch (InterruptedException | IOException e) {
+                               logger.log(ERROR, "Cannot stop service " + serviceName, e);
+                       }
+               }
+       }
+
+       /** A thread watching the login prompt. */
+       static class LoginThread extends Thread {
+               private boolean systemShuttingDown = false;
+               private Process process = null;
+
+               public LoginThread() {
+                       super("FREEd login prompt");
+                       setDaemon(true);
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                               systemShuttingDown = true;
+                               if (process != null)
+                                       process.destroy();
+                       }));
+               }
+
+               @Override
+               public void run() {
+                       boolean getty = true;
+                       prompt: while (!systemShuttingDown) {
+                               try {
+                                       if (getty) {
+                                               ProcessBuilder pb = new ProcessBuilder("/usr/sbin/getty", "38400", "tty2");
+                                               process = pb.start();
+                                       } else {
+                                               Console console = System.console();
+                                               console.readLine(); // type return once to activate login prompt
+                                               console.printf("login: ");
+                                               String username = console.readLine();
+                                               username = username.trim();
+                                               if ("".equals(username))
+                                                       continue prompt;
+                                               ProcessBuilder pb = new ProcessBuilder("su", "--login", username);
+                                               pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+                                               pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+                                               pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
+                                               process = pb.start();
+                                       }
+                                       Runtime.getRuntime().addShutdownHook(new Thread(() -> process.destroy()));
+                                       try {
+                                               process.waitFor();
+                                       } catch (InterruptedException e) {
+                                               process.destroy();
+                                       }
+                               } catch (Exception e) {
+                                       e.printStackTrace();
+                               } finally {
+                                       process = null;
+                               }
+                       }
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java b/org.argeo.init/src/org/argeo/init/a2/A2Branch.java
deleted file mode 100644 (file)
index cd8d895..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.argeo.init.a2;
-
-import java.util.Collections;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.argeo.init.osgi.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/**
- * A logical linear sequence of versions of a given {@link A2Component}. This is
- * typically a combination of major and minor version, indicating backward
- * compatibility.
- */
-public class A2Branch implements Comparable<A2Branch> {
-       private final A2Component component;
-       private final String id;
-
-       final SortedMap<Version, A2Module> modules = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public A2Branch(A2Component component, String id) {
-               this.component = component;
-               this.id = id;
-               component.branches.put(id, this);
-       }
-
-       public Iterable<A2Module> listModules(Object filter) {
-               return modules.values();
-       }
-
-       A2Module getOrAddModule(Version version, Object locator) {
-               if (modules.containsKey(version)) {
-                       A2Module res = modules.get(version);
-                       if (OsgiBootUtils.isDebug() && !res.getLocator().equals(locator)) {
-                               OsgiBootUtils.debug("Inconsistent locator " + locator + " (registered: " + res.getLocator() + ")");
-                       }
-                       return res;
-               } else
-                       return new A2Module(this, version, locator);
-       }
-
-       public A2Module last() {
-               return modules.get(modules.lastKey());
-       }
-
-       public A2Module first() {
-               return modules.get(modules.firstKey());
-       }
-
-       public A2Component getComponent() {
-               return component;
-       }
-
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public int compareTo(A2Branch o) {
-               return id.compareTo(id);
-       }
-
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Branch) {
-                       A2Branch o = (A2Branch) obj;
-                       return component.equals(o.component) && id.equals(o.id);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return getCoordinates();
-       }
-
-       public String getCoordinates() {
-               return component + ":" + id;
-       }
-
-       static String versionToBranchId(Version version) {
-               return version.getMajor() + "." + version.getMinor();
-       }
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Component.java b/org.argeo.init/src/org/argeo/init/a2/A2Component.java
deleted file mode 100644 (file)
index 8942706..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.argeo.init.a2;
-
-import java.util.Collections;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.osgi.framework.Version;
-
-/**
- * The logical name of a software package. In OSGi's case this is
- * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
- * id.
- */
-public class A2Component implements Comparable<A2Component> {
-       private final A2Contribution contribution;
-       private final String id;
-
-       final SortedMap<String, A2Branch> branches = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public A2Component(A2Contribution contribution, String id) {
-               this.contribution = contribution;
-               this.id = id;
-               contribution.components.put(id, this);
-       }
-
-       public Iterable<A2Branch> listBranches(Object filter) {
-               return branches.values();
-       }
-
-       A2Branch getOrAddBranch(String branchId) {
-               if (!branches.containsKey(branchId)) {
-                       A2Branch a2Branch = new A2Branch(this, branchId);
-                       branches.put(branchId, a2Branch);
-               }
-               return branches.get(branchId);
-       }
-
-       A2Module getOrAddModule(Version version, Object locator) {
-               A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version));
-               A2Module module = branch.getOrAddModule(version, locator);
-               return module;
-       }
-
-       public A2Branch last() {
-               return branches.get(branches.lastKey());
-       }
-
-       public A2Contribution getContribution() {
-               return contribution;
-       }
-
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public int compareTo(A2Component o) {
-               return id.compareTo(o.id);
-       }
-
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Component) {
-                       A2Component o = (A2Component) obj;
-                       return contribution.equals(o.contribution) && id.equals(o.id);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return contribution.getId() + ":" + id;
-       }
-
-       void asTree(String prefix, StringBuffer buf) {
-               if (prefix == null)
-                       prefix = "";
-               A2Branch lastBranch = last();
-               SortedMap<String, A2Branch> displayMap = new TreeMap<>(Collections.reverseOrder());
-               displayMap.putAll(branches);
-               for (String branchId : displayMap.keySet()) {
-                       A2Branch branch = displayMap.get(branchId);
-                       if (!lastBranch.equals(branch)) {
-                               buf.append('\n');
-                               buf.append(prefix);
-                       } else {
-                               buf.append(" -");
-                       }
-                       buf.append(prefix);
-                       buf.append(branchId);
-                       A2Module first = branch.first();
-                       A2Module last = branch.last();
-                       buf.append(" (").append(last.getVersion());
-                       if (!first.equals(last))
-                               buf.append(" ... ").append(first.getVersion());
-                       buf.append(')');
-               }
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java
deleted file mode 100644 (file)
index 9de09ce..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-package org.argeo.init.a2;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * A category grouping a set of {@link A2Component}, typically based on the
- * provider of these components. This is the equivalent of Maven's group Id.
- */
-public class A2Contribution implements Comparable<A2Contribution> {
-       final static String BOOT = "boot";
-       final static String RUNTIME = "runtime";
-       final static String CLASSPATH = "classpath";
-
-       final static String DEFAULT = "default";
-       final static String LIB = "lib";
-
-       private final ProvisioningSource source;
-       private final String id;
-
-       final Map<String, A2Component> components = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       /**
-        * The contribution must be added to the source. Rather use
-        * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this
-        * contructor directly.
-        */
-       public A2Contribution(ProvisioningSource context, String id) {
-               this.source = context;
-               this.id = id;
-//             if (context != null)
-//                     context.contributions.put(id, this);
-       }
-
-       public Iterable<A2Component> listComponents(Object filter) {
-               return components.values();
-       }
-
-       A2Component getOrAddComponent(String componentId) {
-               if (components.containsKey(componentId))
-                       return components.get(componentId);
-               else
-                       return new A2Component(this, componentId);
-       }
-
-       public ProvisioningSource getSource() {
-               return source;
-       }
-
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public int compareTo(A2Contribution o) {
-               return id.compareTo(o.id);
-       }
-
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Contribution) {
-                       A2Contribution o = (A2Contribution) obj;
-                       return id.equals(o.id);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return id;
-       }
-
-       void asTree(String prefix, StringBuffer buf) {
-               if (prefix == null)
-                       prefix = "";
-               for (String componentId : components.keySet()) {
-                       buf.append(prefix);
-                       buf.append(componentId);
-                       A2Component component = components.get(componentId);
-                       component.asTree(prefix, buf);
-                       buf.append('\n');
-               }
-       }
-
-       static String localOsArchRelativePath() {
-               return Os.local().toString() + "/" + Arch.local().toString();
-       }
-
-       static enum Os {
-               LINUX, WIN32, MACOSX, UNKOWN;
-
-               @Override
-               public String toString() {
-                       return name().toLowerCase();
-               }
-
-               public static Os local() {
-                       String osStr = System.getProperty("os.name").toLowerCase();
-                       if (osStr.startsWith("linux"))
-                               return LINUX;
-                       if (osStr.startsWith("win"))
-                               return WIN32;
-                       if (osStr.startsWith("mac"))
-                               return MACOSX;
-                       return UNKOWN;
-               }
-
-       }
-
-       static enum Arch {
-               X86_64, AARCH64, X86, POWERPC, UNKOWN;
-
-               @Override
-               public String toString() {
-                       return name().toLowerCase();
-               }
-
-               public static Arch local() {
-                       String archStr = System.getProperty("os.arch").toLowerCase();
-                       return switch (archStr) {
-                       case "x86_64":
-                       case "amd64":
-                       case "x86-64": {
-                               yield X86_64;
-                       }
-                       case "aarch64":
-                       case "arm64": {
-                               yield AARCH64;
-                       }
-                       case "x86":
-                       case "i386":
-                       case "i686": {
-                               yield X86;
-                       }
-                       case "powerpc":
-                       case "ppc": {
-                               yield POWERPC;
-                       }
-                       default:
-                               yield UNKOWN;
-                       };
-               }
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Exception.java b/org.argeo.init/src/org/argeo/init/a2/A2Exception.java
deleted file mode 100644 (file)
index 6ba87a7..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.init.a2;
-
-/** Unchecked A2 provisioning exception. */
-public class A2Exception extends RuntimeException {
-       private static final long serialVersionUID = 1927603558545397360L;
-
-       public A2Exception(String message, Throwable e) {
-               super(message, e);
-       }
-
-       public A2Exception(String message) {
-               super(message);
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Module.java b/org.argeo.init/src/org/argeo/init/a2/A2Module.java
deleted file mode 100644 (file)
index 0b6d3a9..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.init.a2;
-
-import org.osgi.framework.Version;
-
-/**
- * An identified software package. In OSGi's case this is the combination of
- * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
- * equivalent of the full coordinates of a Maven artifact version.
- */
-public class A2Module implements Comparable<A2Module> {
-       private final A2Branch branch;
-       private final Version version;
-       private final Object locator;
-
-       public A2Module(A2Branch branch, Version version, Object locator) {
-               this.branch = branch;
-               this.version = version;
-               this.locator = locator;
-               branch.modules.put(version, this);
-       }
-
-       public A2Branch getBranch() {
-               return branch;
-       }
-
-       public Version getVersion() {
-               return version;
-       }
-
-       Object getLocator() {
-               return locator;
-       }
-
-       @Override
-       public int compareTo(A2Module o) {
-               return version.compareTo(o.version);
-       }
-
-       @Override
-       public int hashCode() {
-               return version.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Module) {
-                       A2Module o = (A2Module) obj;
-                       return branch.equals(o.branch) && version.equals(o.version);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return getCoordinates();
-       }
-
-       public String getCoordinates() {
-               return branch.getComponent() + ":" + version;
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Source.java b/org.argeo.init/src/org/argeo/init/a2/A2Source.java
deleted file mode 100644 (file)
index 5c8329c..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.init.a2;
-
-import java.net.URI;
-
-/** A provisioning source in A2 format. */
-public interface A2Source extends ProvisioningSource {
-       /** Use standard a2 protocol, installing from source URL. */
-       final static String SCHEME_A2 = "a2";
-       /**
-        * Use equinox-specific reference: installation, which does not copy the bundle
-        * content.
-        */
-       final static String SCHEME_A2_REFERENCE = "a2+reference";
-       final static String DEFAULT_A2_URI = SCHEME_A2 + ":///";
-       final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///";
-
-       URI getUri();
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java
deleted file mode 100644 (file)
index f946add..0000000
+++ /dev/null
@@ -1,269 +0,0 @@
-package org.argeo.init.a2;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Collections;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-
-/** Where components are retrieved from. */
-public abstract class AbstractProvisioningSource implements ProvisioningSource {
-       protected final Map<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       private final boolean usingReference;
-
-       public AbstractProvisioningSource(boolean usingReference) {
-               this.usingReference = usingReference;
-       }
-
-       public Iterable<A2Contribution> listContributions(Object filter) {
-               return contributions.values();
-       }
-
-       @Override
-       public Bundle install(BundleContext bc, A2Module module) {
-               try {
-                       Object locator = module.getLocator();
-                       if (usingReference && locator instanceof Path locatorPath) {
-                               String referenceUrl = "reference:file:" + locatorPath.toString();
-                               Bundle bundle = bc.installBundle(referenceUrl);
-                               return bundle;
-                       } else {
-                               Path locatorPath = (Path) locator;
-                               Path pathToUse;
-                               boolean isTemp = false;
-                               if (locator instanceof Path && Files.isDirectory(locatorPath)) {
-                                       pathToUse = toTempJar(locatorPath);
-                                       isTemp = true;
-                               } else {
-                                       pathToUse = locatorPath;
-                               }
-                               Bundle bundle;
-                               try (InputStream in = newInputStream(pathToUse)) {
-                                       bundle = bc.installBundle(locatorPath.toAbsolutePath().toString(), in);
-                               }
-
-                               if (isTemp && pathToUse != null)
-                                       Files.deleteIfExists(pathToUse);
-                               return bundle;
-                       }
-               } catch (BundleException | IOException e) {
-                       throw new A2Exception("Cannot install module " + module, e);
-               }
-       }
-
-       @Override
-       public void update(Bundle bundle, A2Module module) {
-               try {
-                       Object locator = module.getLocator();
-                       if (usingReference && locator instanceof Path) {
-                               try (InputStream in = newInputStream(locator)) {
-                                       bundle.update(in);
-                               }
-                       } else {
-                               Path locatorPath = (Path) locator;
-                               Path pathToUse;
-                               boolean isTemp = false;
-                               if (locator instanceof Path && Files.isDirectory(locatorPath)) {
-                                       pathToUse = toTempJar(locatorPath);
-                                       isTemp = true;
-                               } else {
-                                       pathToUse = locatorPath;
-                               }
-                               try (InputStream in = newInputStream(pathToUse)) {
-                                       bundle.update(in);
-                               }
-                               if (isTemp && pathToUse != null)
-                                       Files.deleteIfExists(pathToUse);
-                       }
-               } catch (BundleException | IOException e) {
-                       throw new A2Exception("Cannot update module " + module, e);
-               }
-       }
-
-       @Override
-       public A2Branch findBranch(String componentId, Version version) {
-               A2Component component = findComponent(componentId);
-               if (component == null)
-                       return null;
-               String branchId = version.getMajor() + "." + version.getMinor();
-               if (!component.branches.containsKey(branchId))
-                       return null;
-               return component.branches.get(branchId);
-       }
-
-       protected A2Contribution getOrAddContribution(String contributionId) {
-               if (contributions.containsKey(contributionId))
-                       return contributions.get(contributionId);
-               else {
-                       A2Contribution contribution = new A2Contribution(this, contributionId);
-                       contributions.put(contributionId, contribution);
-                       return contribution;
-               }
-       }
-
-       protected void asTree(String prefix, StringBuffer buf) {
-               if (prefix == null)
-                       prefix = "";
-               for (String contributionId : contributions.keySet()) {
-                       buf.append(prefix);
-                       buf.append(contributionId);
-                       buf.append('\n');
-                       A2Contribution contribution = contributions.get(contributionId);
-                       contribution.asTree(prefix + " ", buf);
-               }
-       }
-
-       protected void asTree() {
-               StringBuffer buf = new StringBuffer();
-               asTree("", buf);
-               System.out.println(buf);
-       }
-
-       protected A2Component findComponent(String componentId) {
-               SortedMap<A2Contribution, A2Component> res = new TreeMap<>();
-               for (A2Contribution contribution : contributions.values()) {
-                       components: for (String componentIdKey : contribution.components.keySet()) {
-                               if (componentId.equals(componentIdKey)) {
-                                       res.put(contribution, contribution.components.get(componentIdKey));
-                                       break components;
-                               }
-                       }
-               }
-               if (res.size() == 0)
-                       return null;
-               // TODO explicit contribution priorities
-               return res.get(res.lastKey());
-
-       }
-
-       protected String[] readNameVersionFromModule(Path modulePath) {
-               Manifest manifest;
-               if (Files.isDirectory(modulePath)) {
-                       manifest = findManifest(modulePath);
-               } else {
-                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
-                               manifest = in.getManifest();
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
-                       }
-               }
-               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-               int semiColIndex = symbolicName.indexOf(';');
-               if (semiColIndex >= 0)
-                       symbolicName = symbolicName.substring(0, semiColIndex);
-               return new String[] { symbolicName, versionStr };
-       }
-
-       protected String readVersionFromModule(Path modulePath) {
-               Manifest manifest;
-               if (Files.isDirectory(modulePath)) {
-                       manifest = findManifest(modulePath);
-               } else {
-                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
-                               manifest = in.getManifest();
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
-                       }
-               }
-               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-               return versionStr;
-       }
-
-       protected String readSymbolicNameFromModule(Path modulePath) {
-               Manifest manifest;
-               if (Files.isDirectory(modulePath)) {
-                       manifest = findManifest(modulePath);
-               } else {
-                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
-                               manifest = in.getManifest();
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
-                       }
-               }
-               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-               int semiColIndex = symbolicName.indexOf(';');
-               if (semiColIndex >= 0)
-                       symbolicName = symbolicName.substring(0, semiColIndex);
-               return symbolicName;
-       }
-
-       protected boolean isUsingReference() {
-               return usingReference;
-       }
-
-       private InputStream newInputStream(Object locator) throws IOException {
-               if (locator instanceof Path) {
-                       return Files.newInputStream((Path) locator);
-               } else if (locator instanceof URL) {
-                       return ((URL) locator).openStream();
-               } else {
-                       throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
-               }
-       }
-
-       private static Manifest findManifest(Path currentPath) {
-               Path metaInfPath = currentPath.resolve("META-INF");
-               if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) {
-                       Path manifestPath = metaInfPath.resolve("MANIFEST.MF");
-                       try {
-                               try (InputStream in = Files.newInputStream(manifestPath)) {
-                                       Manifest manifest = new Manifest(in);
-                                       return manifest;
-                               }
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + manifestPath, e);
-                       }
-               } else {
-                       Path parentPath = currentPath.getParent();
-                       if (parentPath == null)
-                               throw new A2Exception("MANIFEST.MF file not found.");
-                       return findManifest(currentPath.getParent());
-               }
-       }
-
-       private static Path toTempJar(Path dir) {
-               try {
-                       Manifest manifest = findManifest(dir);
-                       Path jarPath = Files.createTempFile("a2Source", ".jar");
-                       try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
-                               Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
-                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                               Path relPath = dir.relativize(file);
-                                               // skip MANIFEST from folder
-                                               if (relPath.toString().contentEquals("META-INF/MANIFEST.MF"))
-                                                       return FileVisitResult.CONTINUE;
-                                               zos.putNextEntry(new ZipEntry(relPath.toString()));
-                                               Files.copy(file, zos);
-                                               zos.closeEntry();
-                                               return FileVisitResult.CONTINUE;
-                                       }
-                               });
-                       }
-                       return jarPath;
-               } catch (IOException e) {
-                       throw new A2Exception("Cannot install OSGi bundle from " + dir, e);
-               }
-
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java
deleted file mode 100644 (file)
index 12de422..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.argeo.init.a2;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.List;
-
-import org.argeo.init.osgi.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/**
- * A provisioning source based on the linear classpath with which the JVM has
- * been started.
- */
-public class ClasspathSource extends AbstractProvisioningSource {
-       
-       public ClasspathSource() {
-               super(true);
-       }
-
-       void load() throws IOException {
-               A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH);
-               List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
-               parts: for (String part : classpath) {
-                       Path file = Paths.get(part);
-                       Version version;
-                       try {
-                               version = new Version(readVersionFromModule(file));
-                       } catch (Exception e) {
-                               // ignore non OSGi
-                               continue parts;
-                       }
-                       String moduleName = readSymbolicNameFromModule(file);
-                       A2Component component = classpathContribution.getOrAddComponent(moduleName);
-                       A2Module module = component.getOrAddModule(version, file);
-                       if (OsgiBootUtils.isDebug())
-                               OsgiBootUtils.debug("Registered " + module);
-               }
-
-       }
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java
deleted file mode 100644 (file)
index 921992d..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.init.a2;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.StringJoiner;
-import java.util.TreeMap;
-
-import org.argeo.init.osgi.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/** A file system {@link AbstractProvisioningSource} in A2 format. */
-public class FsA2Source extends AbstractProvisioningSource implements A2Source {
-       private final Path base;
-       private final Map<String, String> variantsXOr;
-
-//     public FsA2Source(Path base) {
-//             this(base, new HashMap<>());
-//     }
-
-       public FsA2Source(Path base, Map<String, String> variantsXOr, boolean usingReference) {
-               super(usingReference);
-               this.base = base;
-               this.variantsXOr = new HashMap<>(variantsXOr);
-       }
-
-       void load() throws IOException {
-               SortedMap<Path, A2Contribution> contributions = new TreeMap<>();
-
-               DirectoryStream<Path> contributionPaths = Files.newDirectoryStream(base);
-               contributions: for (Path contributionPath : contributionPaths) {
-                       if (Files.isDirectory(contributionPath)) {
-                               String contributionId = contributionPath.getFileName().toString();
-                               if (A2Contribution.BOOT.equals(contributionId))// skip boot
-                                       continue contributions;
-                               if (contributionId.contains(".")) {
-                                       A2Contribution contribution = getOrAddContribution(contributionId);
-                                       contributions.put(contributionPath, contribution);
-                               } else {// variants
-                                       Path variantPath = null;
-                                       // is it an explicit variant?
-                                       String variant = variantsXOr.get(contributionPath.getFileName().toString());
-                                       if (variant != null) {
-                                               variantPath = contributionPath.resolve(variant);
-                                       }
-
-                                       // is there a default variant?
-                                       if (variantPath == null) {
-                                               Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT);
-                                               if (Files.exists(defaultPath)) {
-                                                       variantPath = defaultPath;
-                                               }
-                                       }
-
-                                       if (variantPath == null)
-                                               continue contributions;
-
-                                       // a variant was found, let's collect its contributions (also common ones in its
-                                       // parent)
-                                       if (Files.exists(variantPath.getParent())) {
-                                               for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) {
-                                                       String variantContributionId = variantContributionPath.getFileName().toString();
-                                                       if (variantContributionId.contains(".")) {
-                                                               A2Contribution contribution = getOrAddContribution(variantContributionId);
-                                                               contributions.put(variantContributionPath, contribution);
-                                                       }
-                                               }
-                                       }
-                                       if (Files.exists(variantPath)) {
-                                               for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) {
-                                                       String variantContributionId = variantContributionPath.getFileName().toString();
-                                                       if (variantContributionId.contains(".")) {
-                                                               A2Contribution contribution = getOrAddContribution(variantContributionId);
-                                                               contributions.put(variantContributionPath, contribution);
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               for (Path contributionPath : contributions.keySet()) {
-                       String contributionId = contributionPath.getFileName().toString();
-                       A2Contribution contribution = getOrAddContribution(contributionId);
-                       DirectoryStream<Path> modulePaths = Files.newDirectoryStream(contributionPath);
-                       modules: for (Path modulePath : modulePaths) {
-                               if (!Files.isDirectory(modulePath)) {
-                                       // OsgiBootUtils.debug("Registering " + modulePath);
-                                       String moduleFileName = modulePath.getFileName().toString();
-                                       int lastDot = moduleFileName.lastIndexOf('.');
-                                       String ext = moduleFileName.substring(lastDot + 1);
-                                       if (!"jar".equals(ext))
-                                               continue modules;
-                                       Version version;
-                                       // TODO optimise? check attributes?
-                                       String[] nameVersion = readNameVersionFromModule(modulePath);
-                                       String componentName = nameVersion[0];
-                                       String versionStr = nameVersion[1];
-                                       if (versionStr != null) {
-                                               version = new Version(versionStr);
-                                       } else {
-                                               OsgiBootUtils.debug("Ignore " + modulePath + " since version cannot be found");
-                                               continue modules;
-                                       }
-//                                     }
-                                       A2Component component = contribution.getOrAddComponent(componentName);
-                                       A2Module module = component.getOrAddModule(version, modulePath);
-                                       if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.debug("Registered " + module);
-                               }
-                       }
-               }
-
-       }
-
-       @Override
-       public URI getUri() {
-               URI baseUri = base.toUri();
-               try {
-                       if (baseUri.getScheme().equals("file")) {
-                               String queryPart = "";
-                               if (!getVariantsXOr().isEmpty()) {
-                                       StringJoiner sj = new StringJoiner("&");
-                                       for (String key : getVariantsXOr().keySet()) {
-                                               sj.add(key + "=" + getVariantsXOr().get(key));
-                                       }
-                                       queryPart = sj.toString();
-                               }
-                               return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart,
-                                               null);
-                       } else {
-                               throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme());
-                       }
-               } catch (URISyntaxException e) {
-                       throw new IllegalStateException("Cannot build URI from " + baseUri, e);
-               }
-       }
-
-       protected Map<String, String> getVariantsXOr() {
-               return variantsXOr;
-       }
-
-//     public static void main(String[] args) {
-//             if (args.length == 0)
-//                     throw new IllegalArgumentException("Usage: <path to A2 base>");
-//             try {
-//                     Map<String, String> xOr = new HashMap<>();
-//                     xOr.put("osgi", "equinox");
-//                     xOr.put("swt", "rap");
-//                     FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
-//                     context.load();
-//                     context.asTree();
-//             } catch (Exception e) {
-//                     e.printStackTrace();
-//             }
-//     }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java
deleted file mode 100644 (file)
index 0313d20..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.argeo.init.a2;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-
-import org.argeo.init.osgi.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */
-public class FsM2Source extends AbstractProvisioningSource {
-       private final Path base;
-
-       public FsM2Source(Path base) {
-               super(false);
-               this.base = base;
-       }
-
-       void load() throws IOException {
-               Files.walkFileTree(base, new ArtifactFileVisitor());
-       }
-
-       class ArtifactFileVisitor extends SimpleFileVisitor<Path> {
-
-               @Override
-               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                       // OsgiBootUtils.debug("Processing " + file);
-                       if (file.toString().endsWith(".jar")) {
-                               Version version;
-                               try {
-                                       version = new Version(readVersionFromModule(file));
-                               } catch (Exception e) {
-                                       // ignore non OSGi
-                                       return FileVisitResult.CONTINUE;
-                               }
-                               String moduleName = readSymbolicNameFromModule(file);
-                               Path groupPath = file.getParent().getParent().getParent();
-                               Path relGroupPath = base.relativize(groupPath);
-                               String contributionName = relGroupPath.toString().replace(File.separatorChar, '.');
-                               A2Contribution contribution = getOrAddContribution(contributionName);
-                               A2Component component = contribution.getOrAddComponent(moduleName);
-                               A2Module module = component.getOrAddModule(version, file);
-                               if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.debug("Registered " + module);
-                       }
-                       return super.visitFile(file, attrs);
-               }
-
-       }
-
-       public static void main(String[] args) {
-               try {
-                       FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository"));
-                       context.load();
-                       context.asTree();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java
deleted file mode 100644 (file)
index 7f1133f..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.init.a2;
-
-import org.argeo.init.osgi.OsgiBootUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.Version;
-
-/**
- * A running OSGi bundle context seen as a {@link AbstractProvisioningSource}.
- */
-class OsgiContext extends AbstractProvisioningSource {
-       private final BundleContext bc;
-
-       private A2Contribution runtimeContribution;
-
-       public OsgiContext(BundleContext bc) {
-               super(false);
-               this.bc = bc;
-               runtimeContribution = getOrAddContribution(A2Contribution.RUNTIME);
-       }
-
-       public OsgiContext() {
-               super(false);
-               Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class);
-               if (bundle == null)
-                       throw new IllegalArgumentException(
-                                       "OSGi Boot bundle must be started or a bundle context must be specified");
-               this.bc = bundle.getBundleContext();
-       }
-
-       void load() {
-               for (Bundle bundle : bc.getBundles()) {
-                       registerBundle(bundle);
-               }
-
-       }
-
-       void registerBundle(Bundle bundle) {
-               String componentId = bundle.getSymbolicName();
-               Version version = bundle.getVersion();
-               A2Component component = runtimeContribution.getOrAddComponent(componentId);
-               A2Module module = component.getOrAddModule(version, bundle);
-               if (OsgiBootUtils.isDebug())
-                       OsgiBootUtils.debug("Registered bundle module " + module + " (location id: " + bundle.getLocation() + ")");
-
-       }
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java
deleted file mode 100644 (file)
index 289870a..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-package org.argeo.init.a2;
-
-import static org.argeo.init.a2.A2Source.SCHEME_A2;
-import static org.argeo.init.a2.A2Source.SCHEME_A2_REFERENCE;
-
-import java.io.File;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.argeo.init.osgi.OsgiBootUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-import org.osgi.framework.wiring.FrameworkWiring;
-
-/** Loads provisioning sources into an OSGi context. */
-public class ProvisioningManager {
-       BundleContext bc;
-       OsgiContext osgiContext;
-       List<ProvisioningSource> sources = Collections.synchronizedList(new ArrayList<>());
-
-       public ProvisioningManager(BundleContext bc) {
-               this.bc = bc;
-               osgiContext = new OsgiContext(bc);
-               osgiContext.load();
-       }
-
-       protected void addSource(ProvisioningSource source) {
-               sources.add(source);
-       }
-
-       void installWholeSource(ProvisioningSource source) {
-               Set<Bundle> updatedBundles = new HashSet<>();
-               for (A2Contribution contribution : source.listContributions(null)) {
-                       for (A2Component component : contribution.components.values()) {
-                               A2Module module = component.last().last();
-                               Bundle bundle = installOrUpdate(module);
-                               if (bundle != null)
-                                       updatedBundles.add(bundle);
-                       }
-               }
-//             FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
-//             frameworkWiring.refreshBundles(updatedBundles);
-       }
-
-       public void registerSource(String uri) {
-               try {
-                       URI u = new URI(uri);
-
-                       // XOR
-                       Map<String, List<String>> properties = queryToMap(u);
-                       Map<String, String> xOr = new HashMap<>();
-                       for (String key : properties.keySet()) {
-                               List<String> lst = properties.get(key);
-                               if (lst.size() != 1)
-                                       throw new IllegalArgumentException("Invalid XOR definitions in " + uri);
-                               xOr.put(key, lst.get(0));
-                       }
-
-                       if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) {
-                               if (u.getHost() == null || "".equals(u.getHost())) {
-                                       String baseStr = u.getPath();
-                                       if (File.separatorChar == '\\') {// MS Windows
-                                               baseStr = baseStr.substring(1).replace('/', File.separatorChar);
-                                       }
-                                       Path base = Paths.get(baseStr);
-                                       if (Files.exists(base)) {
-                                               FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()));
-                                               source.load();
-                                               addSource(source);
-                                               OsgiBootUtils.info("Registered " + uri + " as source");
-
-                                               // OS specific / native
-                                               String localRelPath = A2Contribution.localOsArchRelativePath();
-                                               Path localLibBase = base.resolve(A2Contribution.LIB).resolve(localRelPath);
-                                               if (Files.exists(localLibBase)) {
-                                                       FsA2Source libSource = new FsA2Source(localLibBase, xOr,
-                                                                       SCHEME_A2_REFERENCE.equals(u.getScheme()));
-                                                       libSource.load();
-                                                       addSource(libSource);
-                                                       OsgiBootUtils.info("Registered OS-specific " + uri + " as source (" + localRelPath + ")");
-                                               }
-                                       } else {
-                                               OsgiBootUtils.debug("Source " + base + " does not exist, ignoring.");
-                                       }
-                               } else {
-                                       throw new UnsupportedOperationException(
-                                                       "Remote installation is not yet supported, cannot add source " + u);
-                               }
-                       } else {
-                               throw new IllegalArgumentException("Unkown scheme: for source " + u);
-                       }
-               } catch (Exception e) {
-                       throw new A2Exception("Cannot add source " + uri, e);
-               }
-       }
-
-       public boolean registerDefaultSource() {
-               String frameworkLocation = bc.getProperty("osgi.framework");
-               try {
-                       URI frameworkLocationUri = new URI(frameworkLocation);
-                       if ("file".equals(frameworkLocationUri.getScheme())) {
-                               Path frameworkPath = Paths.get(frameworkLocationUri);
-                               if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) {
-                                       Path base = frameworkPath.getParent().getParent();
-                                       String baseStr = base.toString();
-                                       if (File.separatorChar == '\\')// MS Windows
-                                               baseStr = '/' + baseStr.replace(File.separatorChar, '/');
-                                       URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null);
-                                       registerSource(baseUri.toString());
-                                       OsgiBootUtils.debug("Default source from framework location " + frameworkLocation);
-                                       return true;
-                               }
-                       }
-               } catch (Exception e) {
-                       OsgiBootUtils.error("Cannot register default source based on framework location " + frameworkLocation, e);
-               }
-               return false;
-       }
-
-       public void install(String spec) {
-               if (spec == null) {
-                       for (ProvisioningSource source : sources) {
-                               installWholeSource(source);
-                       }
-               }
-       }
-
-       /** @return the new/updated bundle, or null if nothing was done. */
-       protected Bundle installOrUpdate(A2Module module) {
-               try {
-                       ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource();
-                       Version moduleVersion = module.getVersion();
-                       A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion);
-                       if (osgiBranch == null) {
-                               Bundle bundle = moduleSource.install(bc, module);
-                               // TODO make it more dynamic, based on OSGi APIs
-                               osgiContext.registerBundle(bundle);
-//                             if (OsgiBootUtils.isDebug())
-//                                     OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
-                               return bundle;
-                       } else {
-                               A2Module lastOsgiModule = osgiBranch.last();
-                               int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
-                               if (compare >= 0) {// update (also if same version)
-                                       Bundle bundle = (Bundle) lastOsgiModule.getLocator();
-                                       if (bundle.getBundleId() == 0)// ignore framework bundle
-                                               return null;
-                                       moduleSource.update(bundle, module);
-                                       // TODO make it more dynamic, based on OSGi APIs
-                                       // TODO remove old module? Or keep update history?
-                                       osgiContext.registerBundle(bundle);
-                                       OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
-                                       return bundle;
-                               } else {
-                                       if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.debug("Did not install as bundle module " + module
-                                                               + " since a module with higher version " + lastOsgiModule.getVersion()
-                                                               + " is already installed for branch " + osgiBranch);
-                               }
-                       }
-               } catch (Exception e) {
-                       OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e);
-               }
-               return null;
-       }
-
-       public Collection<Bundle> update() {
-               boolean fragmentsUpdated = false;
-               Set<Bundle> updatedBundles = new HashSet<>();
-               bundles: for (Bundle bundle : bc.getBundles()) {
-                       for (ProvisioningSource source : sources) {
-                               String componentId = bundle.getSymbolicName();
-                               Version version = bundle.getVersion();
-                               A2Branch branch = source.findBranch(componentId, version);
-                               if (branch == null)
-                                       continue bundles;
-                               A2Module module = branch.last();
-                               Version moduleVersion = module.getVersion();
-                               int compare = moduleVersion.compareTo(version);
-                               if (compare > 0) {// update
-                                       try {
-                                               source.update(bundle, module);
-//                                             bundle.update(in);
-                                               String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST);
-                                               if (fragmentHost != null)
-                                                       fragmentsUpdated = true;
-                                               OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
-                                               updatedBundles.add(bundle);
-                                       } catch (Exception e) {
-                                               OsgiBootUtils.error("Cannot update with module " + module, e);
-                                       }
-                               }
-                       }
-               }
-               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
-               if (fragmentsUpdated)// refresh all
-                       frameworkWiring.refreshBundles(null);
-               else
-                       frameworkWiring.refreshBundles(updatedBundles);
-               return updatedBundles;
-       }
-
-       private static Map<String, List<String>> queryToMap(URI uri) {
-               return queryToMap(uri.getQuery());
-       }
-
-       private static Map<String, List<String>> queryToMap(String queryPart) {
-               try {
-                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
-                       if (queryPart == null)
-                               return query_pairs;
-                       final String[] pairs = queryPart.split("&");
-                       for (String pair : pairs) {
-                               final int idx = pair.indexOf("=");
-                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
-                                               : pair;
-                               if (!query_pairs.containsKey(key)) {
-                                       query_pairs.put(key, new LinkedList<String>());
-                               }
-                               final String value = idx > 0 && pair.length() > idx + 1
-                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
-                                               : null;
-                               query_pairs.get(key).add(value);
-                       }
-                       return query_pairs;
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
-               }
-       }
-
-//     public static void main(String[] args) {
-//             if (args.length == 0)
-//                     throw new IllegalArgumentException("Usage: <path to A2 base>");
-//             Map<String, String> configuration = new HashMap<>();
-//             configuration.put("osgi.console", "2323");
-//             configuration.put("org.osgi.framework.bootdelegation",
-//                             "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs");
-//             Framework framework = OsgiBootUtils.launch(configuration);
-//             try {
-//                     ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
-//                     Map<String, String> xOr = new HashMap<>();
-//                     xOr.put("osgi", "equinox");
-//                     xOr.put("swt", "rap");
-//                     FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
-//                     context.load();
-//                     pm.addSource(context);
-//                     if (framework.getBundleContext().getBundles().length == 1) {// initial
-//                             pm.install(null);
-//                     } else {
-//                             pm.update();
-//                     }
-//
-//                     Thread.sleep(2000);
-//
-//                     Bundle[] bundles = framework.getBundleContext().getBundles();
-//                     Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName()));
-//                     for (Bundle b : bundles)
-//                             if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE)
-//                                     System.out.println(b.getSymbolicName() + " " + b.getVersion());
-//                             else
-//                                     System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")");
-//             } catch (Exception e) {
-//                     e.printStackTrace();
-//             } finally {
-//                     try {
-//                             framework.stop();
-//                     } catch (Exception e) {
-//                             e.printStackTrace();
-//                     }
-//             }
-//     }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java
deleted file mode 100644 (file)
index 9935630..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.init.a2;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
-
-/** Where components are retrieved from. */
-public interface ProvisioningSource {
-       /** List all contributions of this source. */
-       Iterable<A2Contribution> listContributions(Object filter);
-
-       /** Install a module in the OSGi runtime. */
-       Bundle install(BundleContext bc, A2Module module);
-
-       /** Update a module in the OSGi runtime. */
-       void update(Bundle bundle, A2Module module);
-
-       /** Finds the {@link A2Branch} related to this component and version. */
-       A2Branch findBranch(String componentId, Version version);
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/a2/package-info.java b/org.argeo.init/src/org/argeo/init/a2/package-info.java
deleted file mode 100644 (file)
index bb8fa6e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** A2 OSGi repository format. */
-package org.argeo.init.a2;
\ No newline at end of file
index e60d22fba1061238e20da6b0071afdfdcf03ca7a..3fa2bc868848baa2ba1528cdd189e4f958a137c6 100644 (file)
@@ -7,14 +7,17 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Publisher;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * Factory for Java system logging. As it has to be a public class in order to
  * be exposed as a service provider, it is also the main entry point for the
  * thin logging system, via static methos.
  */
-public class ThinLoggerFinder extends LoggerFinder {
+public class ThinLoggerFinder extends LoggerFinder
+               implements Consumer<Map<String, Object>>, Supplier<Flow.Publisher<Map<String, Serializable>>> {
        private static ThinLogging logging;
        private static ThinJavaUtilLogging javaUtilLogging;
 
@@ -27,7 +30,10 @@ public class ThinLoggerFinder extends LoggerFinder {
        @Override
        public Logger getLogger(String name, Module module) {
                lazyInit();
-               return logging.getLogger(name, module);
+               Objects.requireNonNull(name);
+               Logger logger = logging.getLogger(name, module);
+               Objects.requireNonNull(logger);
+               return logger;
        }
 
        private static void init() {
@@ -64,16 +70,21 @@ public class ThinLoggerFinder extends LoggerFinder {
                javaUtilLogging.readConfiguration(logging.getLevels());
        }
 
-       public static Consumer<Map<String, Object>> getConfigurationConsumer() {
+       static Consumer<Map<String, Object>> getConfigurationConsumer() {
                Objects.requireNonNull(logging);
                return logging;
        }
 
-       public static Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
+       static Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
                Objects.requireNonNull(logging);
                return logging.getLogEntryPublisher();
        }
 
+       @Override
+       public Publisher<Map<String, Serializable>> get() {
+               return getLogEntryPublisher();
+       }
+
        static void update(Map<String, Object> configuration) {
                if (logging == null)
                        throw new IllegalStateException("Thin logging must be initialized first");
@@ -85,4 +96,17 @@ public class ThinLoggerFinder extends LoggerFinder {
        static Logger getLogger(String name) {
                return logging.getLogger(name, null);
        }
+
+       @Override
+       public void accept(Map<String, Object> t) {
+               if (logging != null) {
+                       // delegate to thin logging
+                       logging.accept(t);
+               } else {
+                       // ignore
+                       // TODO try to congure Java logging ?
+               }
+
+       }
+
 }
index ff602ad51a7c06abe532fc5af3111f9c18870a93..dd6fad2e3a5a54c261517b02f720cacf9f925278 100644 (file)
@@ -29,8 +29,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
-import org.argeo.init.RuntimeContext;
-import org.argeo.init.Service;
+import org.argeo.api.init.RuntimeContext;
+import org.argeo.internal.init.InternalState;
 
 /**
  * A thin logging system based on the {@link Logger} framework. It is a
@@ -68,7 +68,7 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        // we don't synchronize maps on purpose as it would be
        // too expensive during normal operation
        // updates to the config may be shortly inconsistent
-       private SortedMap<String, ThinLogger> loggers = new TreeMap<>();
+       private SortedMap<String, ThinLogger> loggers = Collections.synchronizedSortedMap(new TreeMap<>());
        private NavigableMap<String, Level> levels = new TreeMap<>();
        private volatile boolean updatingConfiguration = false;
 
@@ -150,7 +150,7 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        }
 
        private void close() {
-               RuntimeContext runtimeContext = Service.getRuntimeContext();
+               RuntimeContext runtimeContext = InternalState.getMainRuntimeContext();
                if (runtimeContext != null) {
                        try {
                                runtimeContext.waitForStop(0);
@@ -191,9 +191,11 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        }
 
        public Logger getLogger(String name, Module module) {
+               Objects.requireNonNull(name, "logger name");
                if (!loggers.containsKey(name)) {
                        ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name));
                        loggers.put(name, logger);
+                       return logger;
                }
                return loggers.get(name);
        }
@@ -393,11 +395,11 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                        case "org.osgi.service.log.Logger":
                                        case "org.eclipse.osgi.internal.log.LoggerImpl":
                                        case "org.argeo.api.cms.CmsLog":
-                                       case "org.argeo.init.osgi.OsgiBootUtils":
-                                       case "org.slf4j.impl.ArgeoLogger":
-                                       case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
                                        case "org.eclipse.jetty.util.log.Slf4jLog":
                                        case "sun.util.logging.internal.LoggingProviderImpl$JULWrapper":
+                                       case "org.slf4j.impl.ArgeoLogger":
+                                       case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
+                                       case "org.argeo.init.osgi.OsgiBootUtils":
                                                lowestLoggerInterface = i;
                                                continue stack;
                                        default:
@@ -609,24 +611,24 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                }
        }
 
-       public static void main(String args[]) {
-               Logger logger = System.getLogger(ThinLogging.class.getName());
-               logger.log(Logger.Level.ALL, "Log all");
-               logger.log(Logger.Level.TRACE, "Multi\nline\ntrace");
-               logger.log(Logger.Level.DEBUG, "Log debug");
-               logger.log(Logger.Level.INFO, "Log info");
-               logger.log(Logger.Level.WARNING, "Log warning");
-               logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
-
-               try {
-                       // we ait a bit in order to make sure all messages are flushed
-                       // TODO synchronize more efficiently
-                       // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
-                       ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-
-       }
+//     public static void main(String args[]) {
+//             Logger logger = System.getLogger(ThinLogging.class.getName());
+//             logger.log(Logger.Level.ALL, "Log all");
+//             logger.log(Logger.Level.TRACE, "Multi\nline\ntrace");
+//             logger.log(Logger.Level.DEBUG, "Log debug");
+//             logger.log(Logger.Level.INFO, "Log info");
+//             logger.log(Logger.Level.WARNING, "Log warning");
+//             logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
+//
+//             try {
+//                     // we wait a bit in order to make sure all messages are flushed
+//                     // TODO synchronize more efficiently
+//                     // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+//                     ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
+//             } catch (InterruptedException e) {
+//                     // silent
+//             }
+//
+//     }
 
 }
index b85b248b9e17c94f01be20070cb86e69f3a79dd4..057c2178663f7a11ec5288eff28013133469e8e0 100644 (file)
@@ -4,7 +4,7 @@ import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
 import java.util.Objects;
 
-import org.argeo.init.Service;
+import org.argeo.init.ServiceMain;
 import org.argeo.init.logging.ThinLoggerFinder;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -29,7 +29,7 @@ public class Activator implements BundleActivator {
 
        public void start(final BundleContext bundleContext) throws Exception {
                // The OSGi runtime was created by us, and therefore already initialized
-               argeoInit = Boolean.parseBoolean(bundleContext.getProperty(Service.PROP_ARGEO_INIT_MAIN));
+               argeoInit = Boolean.parseBoolean(bundleContext.getProperty(ServiceMain.PROP_ARGEO_INIT_MAIN));
                if (!argeoInit) {
                        if (runtimeContext == null) {
                                runtimeContext = new OsgiRuntimeContext(bundleContext);
diff --git a/org.argeo.init/src/org/argeo/init/osgi/AdminThread.java b/org.argeo.init/src/org/argeo/init/osgi/AdminThread.java
deleted file mode 100644 (file)
index e493e2c..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.argeo.init.osgi;
-
-import java.io.File;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.launch.Framework;
-
-/** Monitors the runtime and can shut it down. */
-@Deprecated
-public class AdminThread extends Thread {
-       public final static String PROP_ARGEO_OSGI_SHUTDOWN_FILE = "argeo.osgi.shutdownFile";
-       private File shutdownFile;
-       private final BundleContext bundleContext;
-
-       public AdminThread(BundleContext bundleContext) {
-               super("OSGi Boot Admin");
-               this.bundleContext = bundleContext;
-               if (System.getProperty(PROP_ARGEO_OSGI_SHUTDOWN_FILE) != null) {
-                       shutdownFile = new File(
-                                       System.getProperty(PROP_ARGEO_OSGI_SHUTDOWN_FILE));
-                       if (!shutdownFile.exists()) {
-                               shutdownFile = null;
-                               OsgiBootUtils.warn("Shutdown file " + shutdownFile
-                                               + " not found, feature deactivated");
-                       }
-               }
-       }
-
-       public void run() {
-               if (shutdownFile != null) {
-                       // wait for file to be removed
-                       while (shutdownFile.exists()) {
-                               try {
-                                       Thread.sleep(1000);
-                               } catch (InterruptedException e) {
-                                       e.printStackTrace();
-                               }
-                       }
-
-                       Framework framework = (Framework) bundleContext.getBundle(0);
-                       try {
-                               // shutdown framework
-                               framework.stop();
-                               // wait 10 mins for shutdown
-                               framework.waitForStop(10 * 60 * 1000);
-                               // close VM
-                               System.exit(0);
-                       } catch (Exception e) {
-                               e.printStackTrace();
-                               System.exit(1);
-                       }
-               }
-       }
-}
index 863ee00841f335957baa25d8fbac2448eed2d977..6a13e6749b04e1e2817673173167ebfc8dfcc2d7 100644 (file)
@@ -1,13 +1,18 @@
 package org.argeo.init.osgi;
 
+import static java.lang.System.Logger.Level.TRACE;
+
 import java.io.File;
 import java.io.IOException;
+import java.lang.System.Logger;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.StringTokenizer;
 
 /** Intermediary structure used by path matching */
 class BundlesSet {
+       private final static Logger logger = System.getLogger(BundlesSet.class.getName());
+
        private String baseUrl = "reference:file";// not used yet
        private final String dir;
        private List<String> includes = new ArrayList<String>();
@@ -25,8 +30,7 @@ class BundlesSet {
                                dirPath = dirPath.substring("file:".length());
 
                        dir = new File(dirPath.replace('/', File.separatorChar)).getCanonicalPath();
-                       if (OsgiBootUtils.isDebug())
-                               OsgiBootUtils.debug("Base dir: " + dir);
+                       logger.log(TRACE, () -> "Base dir: " + dir);
                } catch (IOException e) {
                        throw new RuntimeException("Cannot convert to absolute path", e);
                }
index 35b66e6b70bf45a6c745142bfb557ee39d441bd6..937c3881b7dbdd99f0b7739ee5fef452fdde80b1 100644 (file)
@@ -1,9 +1,12 @@
 package org.argeo.init.osgi;
 
+import static java.lang.System.Logger.Level.WARNING;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.System.Logger;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
@@ -32,6 +35,8 @@ import org.osgi.framework.Version;
  * name of the URL and of the content of the index.
  */
 public class DistributionBundle {
+       private final static Logger logger = System.getLogger(DistributionBundle.class.getName());
+
        private final static String INDEX_FILE_NAME = "modularDistribution.csv";
 
        private final String url;
@@ -113,7 +118,7 @@ public class DistributionBundle {
        public void processUrl() {
                JarInputStream jarIn = null;
                try {
-                       URL u = new URL(url);
+                       URL u = new URI(url).toURL();
 
                        // local cache
                        URI localUri = new URI(localCache + relativeUrl);
@@ -213,7 +218,7 @@ public class DistributionBundle {
                        try {
                                localUri = new URI(localCache + relativeUrl);
                        } catch (URISyntaxException e) {
-                               OsgiBootUtils.warn(e.getMessage());
+                               logger.log(WARNING, e.getMessage());
                                localUri = null;
                        }
                        Version version = new Version(osgiArtifact.getVersion());
diff --git a/org.argeo.init/src/org/argeo/init/osgi/Launcher.java b/org.argeo.init/src/org/argeo/init/osgi/Launcher.java
deleted file mode 100644 (file)
index 778c08a..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.argeo.init.osgi;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.osgi.framework.BundleContext;
-
-/** An OSGi launcher executing first another class in the system class path. */
-public class Launcher {
-
-       public static void main(String[] args) {
-               // Try to load system properties
-               String systemPropertiesFilePath = getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE);
-               if (systemPropertiesFilePath != null) {
-                       FileInputStream in;
-                       try {
-                               in = new FileInputStream(systemPropertiesFilePath);
-                               System.getProperties().load(in);
-                       } catch (IOException e1) {
-                               throw new RuntimeException("Cannot load system properties from " + systemPropertiesFilePath, e1);
-                       }
-                       if (in != null) {
-                               try {
-                                       in.close();
-                               } catch (Exception e) {
-                                       // silent
-                               }
-                       }
-               }
-
-               // Start main class
-               startMainClass();
-
-               // Start Equinox
-               BundleContext bundleContext = null;
-               try {
-                       bundleContext = OsgiBootUtils.launch(OsgiBootUtils.equinoxArgsToConfiguration(args)).getBundleContext();
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot start Equinox.", e);
-               }
-
-               // OSGi bootstrap
-               OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
-               osgiBoot.bootstrap();
-       }
-
-       protected static void startMainClass() {
-               String className = getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS);
-               if (className == null)
-                       return;
-
-               String line = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPARGS, "");
-
-               String[] uiArgs = readArgumentsFromLine(line);
-
-               try {
-                       // Launch main method using reflection
-                       Class<?> clss = Class.forName(className);
-                       Class<?>[] mainArgsClasses = new Class[] { uiArgs.getClass() };
-                       Object[] mainArgs = { uiArgs };
-                       Method mainMethod = clss.getMethod("main", mainArgsClasses);
-                       mainMethod.invoke(null, mainArgs);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot start main class.", e);
-               }
-
-       }
-
-       /**
-        * Transform a line into an array of arguments, taking "" as single arguments.
-        * (nested \" are not supported)
-        */
-       private static String[] readArgumentsFromLine(String lineOrig) {
-               String line = lineOrig.trim();// remove trailing spaces
-               List<String> args = new ArrayList<String>();
-               StringBuffer curr = new StringBuffer("");
-               boolean inQuote = false;
-               char[] arr = line.toCharArray();
-               for (int i = 0; i < arr.length; i++) {
-                       char c = arr[i];
-                       switch (c) {
-                       case '\"':
-                               inQuote = !inQuote;
-                               break;
-                       case ' ':
-                               if (!inQuote) {// otherwise, no break: goes to default
-                                       if (curr.length() > 0) {
-                                               args.add(curr.toString());
-                                               curr = new StringBuffer("");
-                                       }
-                                       break;
-                               }
-                       default:
-                               curr.append(c);
-                               break;
-                       }
-               }
-
-               // Add last arg
-               if (curr.length() > 0) {
-                       args.add(curr.toString());
-                       curr = null;
-               }
-
-               String[] res = new String[args.size()];
-               for (int i = 0; i < args.size(); i++) {
-                       res[i] = args.get(i).toString();
-               }
-               return res;
-       }
-
-       public static String getProperty(String name, String defaultValue) {
-               final String value;
-               if (defaultValue != null)
-                       value = System.getProperty(name, defaultValue);
-               else
-                       value = System.getProperty(name);
-
-               if (value == null || value.equals(""))
-                       return null;
-               else
-                       return value;
-       }
-
-       public static String getProperty(String name) {
-               return getProperty(name, null);
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/Main.java b/org.argeo.init/src/org/argeo/init/osgi/Main.java
deleted file mode 100644 (file)
index ce83329..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.init.osgi;
-
-import java.lang.management.ManagementFactory;
-
-public class Main {
-
-       public static void main(String[] args) {
-               String mainClass = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS);
-               if (mainClass == null) {
-                       throw new IllegalArgumentException(
-                                       "System property " + OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS + " must be specified");
-               }
-
-               OsgiBuilder osgi = new OsgiBuilder();
-               String distributionUrl = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_DISTRIBUTION_URL);
-               if (distributionUrl != null)
-                       osgi.install(distributionUrl);
-               // osgi.conf("argeo.node.useradmin.uris", "os:///");
-               // osgi.conf("osgi.clean", "true");
-               // osgi.conf("osgi.console", "true");
-               osgi.launch();
-
-               if (OsgiBootUtils.isDebug()) {
-                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-                       String jvmUptimeStr = (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-                       OsgiBootUtils.debug("Ready to launch " + mainClass + " in " + jvmUptimeStr);
-               }
-
-               osgi.main(mainClass, args);
-
-               osgi.shutdown();
-
-       }
-
-}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/NodeRunner.java b/org.argeo.init/src/org/argeo/init/osgi/NodeRunner.java
deleted file mode 100644 (file)
index 3369650..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-package org.argeo.init.osgi;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.ServiceLoader;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.launch.Framework;
-import org.osgi.framework.launch.FrameworkFactory;
-
-/** Launch an OSGi framework and deploy a CMS Node into it. */
-public class NodeRunner {
-       private Long timeout = 30 * 1000l;
-       private final Path baseDir;
-       private final Path confDir;
-       private final Path dataDir;
-
-       private String baseUrl = "http://forge.argeo.org/data/java/argeo-2.1/";
-       private String distributionUrl = null;
-
-       private Framework framework = null;
-
-       public NodeRunner(String distributionUrl, Path baseDir) {
-               this.distributionUrl = distributionUrl;
-               Path mavenBase = Paths.get(System.getProperty("user.home") + "/.m2/repository");
-               Path osgiBase = Paths.get("/user/share/osgi");
-               if (Files.exists(mavenBase)) {
-                       Path mavenPath = mavenBase.resolve(distributionUrl);
-                       if (Files.exists(mavenPath))
-                               baseUrl = mavenBase.toUri().toString();
-               } else if (Files.exists(osgiBase)) {
-                       Path osgiPath = osgiBase.resolve(distributionUrl);
-                       if (Files.exists(osgiPath))
-                               baseUrl = osgiBase.toUri().toString();
-               }
-
-               this.baseDir = baseDir;
-               this.confDir = this.baseDir.resolve("state");
-               this.dataDir = this.baseDir.resolve("data");
-
-       }
-
-       public void start() {
-               long begin = System.currentTimeMillis();
-               // log4j
-               Path log4jFile = confDir.resolve("log4j.properties");
-               if (!Files.exists(log4jFile))
-                       copyResource("/org/argeo/osgi/boot/log4j.properties", log4jFile);
-               System.setProperty("log4j.configuration", "file://" + log4jFile.toAbsolutePath());
-
-               // Start Equinox
-               try {
-                       ServiceLoader<FrameworkFactory> ff = ServiceLoader.load(FrameworkFactory.class);
-                       FrameworkFactory frameworkFactory = ff.iterator().next();
-                       Map<String, String> configuration = new HashMap<String, String>();
-                       configuration.put("osgi.configuration.area", confDir.toAbsolutePath().toString());
-                       configuration.put("osgi.instance.area", dataDir.toAbsolutePath().toString());
-                       defaultConfiguration(configuration);
-
-                       framework = frameworkFactory.newFramework(configuration);
-                       framework.start();
-                       info("## Date : " + new Date());
-                       info("## Data : " + dataDir.toAbsolutePath());
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot start OSGi framework", e);
-               }
-               BundleContext bundleContext = framework.getBundleContext();
-               try {
-
-                       // Spring configs currently require System properties
-                       // System.getProperties().putAll(configuration);
-
-                       // expected by JAAS as System.property FIXME
-                       System.setProperty("osgi.instance.area", bundleContext.getProperty("osgi.instance.area"));
-
-                       // OSGi bootstrap
-                       OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
-
-                       osgiBoot.installUrls(osgiBoot.getDistributionUrls(distributionUrl, baseUrl));
-
-                       // Start runtime
-                       Properties startProperties = new Properties();
-                       // TODO make it possible to override it
-                       startProperties.put("argeo.osgi.start.2.node",
-                                       "org.eclipse.equinox.http.servlet,org.eclipse.equinox.http.jetty,"
-                                                       + "org.eclipse.equinox.metatype,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi");
-                       startProperties.put("argeo.osgi.start.3.node", "org.argeo.cms");
-                       startProperties.put("argeo.osgi.start.4.node",
-                                       "org.eclipse.gemini.blueprint.extender,org.eclipse.equinox.http.registry");
-                       osgiBoot.startBundles(startProperties);
-
-                       // Find node repository
-                       ServiceReference<?> sr = null;
-                       while (sr == null) {
-                               sr = bundleContext.getServiceReference("javax.jcr.Repository");
-                               if (System.currentTimeMillis() - begin > timeout)
-                                       throw new RuntimeException("Could find node after " + timeout + "ms");
-                               Thread.sleep(100);
-                       }
-                       Object nodeDeployment = bundleContext.getService(sr);
-                       info("Node Deployment " + nodeDeployment);
-
-                       // Initialization completed
-                       long duration = System.currentTimeMillis() - begin;
-                       info("## CMS Launcher initialized in " + (duration / 1000) + "s " + (duration % 1000) + "ms");
-               } catch (Exception e) {
-                       shutdown();
-                       throw new RuntimeException("Cannot start CMS", e);
-               } finally {
-
-               }
-       }
-
-       private void defaultConfiguration(Map<String, String> configuration) {
-               // all permissions to OSGi security manager
-               Path policyFile = confDir.resolve("node.policy");
-               if (!Files.exists(policyFile))
-                       copyResource("/org/argeo/osgi/boot/node.policy", policyFile);
-               configuration.put("java.security.policy", "file://" + policyFile.toAbsolutePath());
-
-               configuration.put("org.eclipse.rap.workbenchAutostart", "false");
-               configuration.put("org.eclipse.equinox.http.jetty.autostart", "false");
-               configuration.put("org.osgi.framework.bootdelegation",
-                               "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,"
-                                               + "com.sun.nio.file,com.sun.nio.sctp");
-
-               // Do clean
-               // configuration.put("osgi.clean", "true");
-               // if (args.length == 0) {
-               // configuration.put("osgi.console", "");
-               // }
-       }
-
-       public void shutdown() {
-               try {
-                       framework.stop();
-                       framework.waitForStop(15 * 1000);
-               } catch (Exception silent) {
-               }
-       }
-
-       public Path getConfDir() {
-               return confDir;
-       }
-
-       public Path getDataDir() {
-               return dataDir;
-       }
-
-       public Framework getFramework() {
-               return framework;
-       }
-
-       public static void main(String[] args) {
-               try {
-                       String distributionUrl;
-                       Path executionDir;
-                       if (args.length == 2) {
-                               distributionUrl = args[0];
-                               executionDir = Paths.get(args[1]);
-                       } else if (args.length == 1) {
-                               executionDir = Paths.get(System.getProperty("user.dir"));
-                               distributionUrl = args[0];
-                       } else if (args.length == 0) {
-                               executionDir = Paths.get(System.getProperty("user.dir"));
-                               distributionUrl = "org/argeo/commons/org.argeo.dep.cms.sdk/2.1.70/org.argeo.dep.cms.sdk-2.1.70.jar";
-                       }else{
-                               printUsage();
-                               System.exit(1);
-                               return;
-                       }
-
-                       NodeRunner nodeRunner = new NodeRunner(distributionUrl, executionDir);
-                       nodeRunner.start();
-//                     if (args.length != 0)
-//                             System.exit(0);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-
-       protected static void info(Object msg) {
-               System.out.println(msg);
-       }
-
-       protected static void err(Object msg) {
-               System.err.println(msg);
-       }
-
-       protected static void debug(Object msg) {
-               System.out.println(msg);
-       }
-
-       protected static void copyResource(String resource, Path targetFile) {
-               InputStream input = null;
-               OutputStream output = null;
-               try {
-                       input = NodeRunner.class.getResourceAsStream(resource);
-                       Files.createDirectories(targetFile.getParent());
-                       output = Files.newOutputStream(targetFile);
-                       byte[] buf = new byte[8192];
-                       while (true) {
-                               int length = input.read(buf);
-                               if (length < 0)
-                                       break;
-                               output.write(buf, 0, length);
-                       }
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot write " + resource + " file to " + targetFile, e);
-               } finally {
-                       try {
-                               input.close();
-                       } catch (Exception ignore) {
-                       }
-                       try {
-                               output.close();
-                       } catch (Exception ignore) {
-                       }
-               }
-
-       }
-
-       static void printUsage(){
-               err("Usage: <distribution url> <base dir>");
-       }
-}
index f5b260caba404735f9f296830bab0e6e29c369bf..963bab4d495e4349bcc7ba409141deb7e2fa94b7 100644 (file)
@@ -1,32 +1,41 @@
 package org.argeo.init.osgi;
 
-import static org.argeo.init.osgi.OsgiBootUtils.debug;
-import static org.argeo.init.osgi.OsgiBootUtils.warn;
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.TRACE;
+import static java.lang.System.Logger.Level.WARNING;
 
 import java.io.File;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.PathMatcher;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
+import java.util.Optional;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 
-import org.argeo.init.a2.A2Source;
-import org.argeo.init.a2.ProvisioningManager;
+import org.argeo.api.a2.A2Source;
+import org.argeo.api.a2.ProvisioningManager;
+import org.argeo.api.init.InitConstants;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.FrameworkEvent;
 import org.osgi.framework.Version;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
 import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.startlevel.FrameworkStartLevel;
 import org.osgi.framework.wiring.FrameworkWiring;
@@ -36,10 +45,8 @@ import org.osgi.framework.wiring.FrameworkWiring;
  * properties. The approach is to generate list of URLs based on various
  * methods, configured via properties.
  */
-public class OsgiBoot implements OsgiBootConstants {
-       public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start";
-       public final static String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel";
-       public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources";
+public class OsgiBoot {
+       private final static Logger logger = System.getLogger(OsgiBoot.class.getName());
 
        @Deprecated
        final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
@@ -59,18 +66,6 @@ public class OsgiBoot implements OsgiBootConstants {
        public final static String DEFAULT_BASE_URL = "reference:file:";
        final static String DEFAULT_MAX_START_LEVEL = "32";
 
-       // OSGi standard properties
-       final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel";
-       final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel";
-       public final static String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area";
-       public final static String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
-       public final static String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area";
-       public final static String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties";
-
-       // Symbolic names
-       final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.init";
-       final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
-
        private final BundleContext bundleContext;
        private final String localCache;
        private final ProvisioningManager provisioningManager;
@@ -86,7 +81,7 @@ public class OsgiBoot implements OsgiBootConstants {
                localCache = getProperty(PROP_ARGEO_OSGI_LOCAL_CACHE, homeUri + ".m2/repository/");
 
                provisioningManager = new ProvisioningManager(bundleContext);
-               String sources = getProperty(PROP_ARGEO_OSGI_SOURCES);
+               String sources = getProperty(InitConstants.PROP_ARGEO_OSGI_SOURCES);
                if (sources == null) {
                        provisioningManager.registerDefaultSource();
                } else {
@@ -104,19 +99,19 @@ public class OsgiBoot implements OsgiBootConstants {
                                                provisioningManager.registerSource(
                                                                A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2" + queryPart);
                                        provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2" + queryPart);
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/lib/a2" + queryPart);
+//                                     provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/lib/a2" + queryPart);
                                        provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2" + queryPart);
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/lib/a2" + queryPart);
+//                                     provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/lib/a2" + queryPart);
                                } else if (source.trim().equals(A2Source.DEFAULT_A2_REFERENCE_URI)) {
                                        if (Files.exists(homePath))
                                                provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + "://" + homePath.toString()
                                                                + "/.local/share/a2" + queryPart);
                                        provisioningManager
                                                        .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/share/a2" + queryPart);
-                                       provisioningManager
-                                                       .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/lib/a2" + queryPart);
+//                                     provisioningManager
+//                                                     .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/lib/a2" + queryPart);
                                        provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/share/a2" + queryPart);
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/lib/a2" + queryPart);
+//                                     provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/lib/a2" + queryPart);
                                } else {
                                        provisioningManager.registerSource(source + queryPart);
                                }
@@ -136,74 +131,61 @@ public class OsgiBoot implements OsgiBootConstants {
         * with {@link BundleContext#getProperty(String)}. If these properties are
         * <code>null</code>, system properties are used instead.
         */
-       public void bootstrap(Map<String, String> properties) {
+       public void bootstrap() {
                try {
                        long begin = System.currentTimeMillis();
-
-                       // notify start
-                       String osgiInstancePath = getProperty(PROP_OSGI_INSTANCE_AREA);
-                       String osgiConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA);
-                       String osgiSharedConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA);
-                       OsgiBootUtils.info("OSGi bootstrap starting" //
-                                       + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
-                                       + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
-                                       + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
-                       );
-
-                       // legacy install bundles
-                       installUrls(getBundlesUrls());
-                       installUrls(getDistributionUrls());
-
-                       // A2 install bundles
-                       provisioningManager.install(null);
-
+                       // Install bundles
+                       install();
                        // Make sure fragments are properly considered by refreshing
-                       refreshFramework();
-
-                       // start bundles
-//                     if (properties != null && !Boolean.parseBoolean(properties.get(PROP_OSGI_USE_SYSTEM_PROPERTIES)))
-                       startBundles(properties);
-//                     else
-//                             startBundles();
-
-                       // complete
+                       refresh();
+                       // Start bundles
+                       startBundles();
                        long duration = System.currentTimeMillis() - begin;
-                       OsgiBootUtils.info("OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
+                       logger.log(DEBUG, () -> "OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
                                        + duration + "ms), " + bundleContext.getBundles().length + " bundles");
                } catch (RuntimeException e) {
-                       OsgiBootUtils.error("OSGi bootstrap FAILED", e);
+                       logger.log(ERROR, "OSGi bootstrap FAILED", e);
                        throw e;
                }
 
                // diagnostics
-               if (OsgiBootUtils.isDebug()) {
+               if (logger.isLoggable(TRACE)) {
                        OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext);
                        diagnostics.checkUnresolved();
                        Map<String, Set<String>> duplicatePackages = diagnostics.findPackagesExportedTwice();
                        if (duplicatePackages.size() > 0) {
-                               OsgiBootUtils.info("Packages exported twice:");
+                               logger.log(TRACE, "Packages exported twice:");
                                Iterator<String> it = duplicatePackages.keySet().iterator();
                                while (it.hasNext()) {
                                        String pkgName = it.next();
-                                       OsgiBootUtils.info(pkgName);
+                                       logger.log(TRACE, pkgName);
                                        Set<String> bdles = duplicatePackages.get(pkgName);
                                        Iterator<String> bdlesIt = bdles.iterator();
                                        while (bdlesIt.hasNext())
-                                               OsgiBootUtils.info("  " + bdlesIt.next());
+                                               logger.log(TRACE, "  " + bdlesIt.next());
                                }
                        }
                }
                System.out.println();
        }
 
-       /**
-        * Calls {@link #bootstrap(Map)} with <code>null</code>.
-        * 
-        * @see #bootstrap(Map)
-        */
-       @Deprecated
-       public void bootstrap() {
-               bootstrap(null);
+       public void install() {
+               String osgiInstancePath = getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA);
+               String osgiConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
+               String osgiSharedConfigurationPath = getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA);
+               logger.log(DEBUG, () -> "OSGi bootstrap starting" //
+                               + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
+                               + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
+                               + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
+               );
+
+               // legacy install bundles
+               installUrls(getBundlesUrls());
+               installUrls(getDistributionUrls());
+
+               // A2 install bundles
+               provisioningManager.install(null);
+
        }
 
        public void update() {
@@ -236,21 +218,20 @@ public class OsgiBoot implements OsgiBootConstants {
                try {
                        if (installedBundles.containsKey(url)) {
                                Bundle bundle = (Bundle) installedBundles.get(url);
-                               if (OsgiBootUtils.isDebug())
-                                       debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url);
-                       } else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/")
-                                       || url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) {
-                               if (OsgiBootUtils.isDebug())
-                                       warn("Skip " + url);
+                               logger.log(TRACE, () -> "Bundle " + bundle.getSymbolicName() + " already installed from " + url);
+                       } else if (url.contains("/" + InitConstants.SYMBOLIC_NAME_EQUINOX + "/")
+                                       || url.contains("/" + InitConstants.SYMBOLIC_NAME_INIT + "/")) {
+                               if (logger.isLoggable(TRACE))
+                                       logger.log(WARNING, "Skip " + url);
                                return;
                        } else {
                                Bundle bundle = bundleContext.installBundle(url);
                                if (url.startsWith("http"))
-                                       OsgiBootUtils
-                                                       .info("Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
-                               else if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.debug(
-                                                       "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
+                                       logger.log(DEBUG,
+                                                       () -> "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
+                               else
+                                       logger.log(TRACE,
+                                                       () -> "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
                                assert bundle.getSymbolicName() != null;
                                // uninstall previous versions
                                bundles: for (Bundle b : bundleContext.getBundles()) {
@@ -265,17 +246,17 @@ public class OsgiBoot implements OsgiBootConstants {
                                                        if (bundleV.getMicro() > bV.getMicro()) {
                                                                // uninstall older bundles
                                                                b.uninstall();
-                                                               OsgiBootUtils.debug("Uninstalled " + b);
+                                                               logger.log(TRACE, () -> "Uninstalled " + b);
                                                        } else if (bundleV.getMicro() < bV.getMicro()) {
                                                                // uninstall just installed bundle if newer
                                                                bundle.uninstall();
-                                                               OsgiBootUtils.debug("Uninstalled " + bundle);
+                                                               logger.log(TRACE, () -> "Uninstalled " + bundle);
                                                                break bundles;
                                                        } else {
                                                                // uninstall any other with same major/minor
                                                                if (!bundleV.getQualifier().equals(bV.getQualifier())) {
                                                                        b.uninstall();
-                                                                       OsgiBootUtils.debug("Uninstalled " + b);
+                                                                       logger.log(TRACE, () -> "Uninstalled " + b);
                                                                }
                                                        }
                                                }
@@ -285,19 +266,19 @@ public class OsgiBoot implements OsgiBootConstants {
                } catch (BundleException e) {
                        final String ALREADY_INSTALLED = "is already installed";
                        String message = e.getMessage();
-                       if ((message.contains("Bundle \"" + SYMBOLIC_NAME_OSGI_BOOT + "\"")
-                                       || message.contains("Bundle \"" + SYMBOLIC_NAME_EQUINOX + "\""))
+                       if ((message.contains("Bundle \"" + InitConstants.SYMBOLIC_NAME_INIT + "\"")
+                                       || message.contains("Bundle \"" + InitConstants.SYMBOLIC_NAME_EQUINOX + "\""))
                                        && message.contains(ALREADY_INSTALLED)) {
                                // silent, in order to avoid warnings: we know that both
                                // have already been installed...
                        } else {
                                if (message.contains(ALREADY_INSTALLED)) {
-                                       if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.warn("Duplicate install from " + url + ": " + message);
+                                       if (logger.isLoggable(TRACE))
+                                               logger.log(WARNING, "Duplicate install from " + url + ": " + message);
                                } else
-                                       OsgiBootUtils.warn("Could not install bundle from " + url + ": " + message);
+                                       logger.log(WARNING, "Could not install bundle from " + url + ": " + message);
                        }
-                       if (OsgiBootUtils.isDebug() && !message.contains(ALREADY_INSTALLED))
+                       if (logger.isLoggable(TRACE) && !message.contains(ALREADY_INSTALLED))
                                e.printStackTrace();
                }
        }
@@ -311,65 +292,70 @@ public class OsgiBoot implements OsgiBootConstants {
         * 
         * @see OsgiBoot#doStartBundles(Map)
         */
-       public void startBundles(Map<String, String> properties) {
+       public void startBundles() {
                Map<String, String> map = new TreeMap<>();
                // first use properties
-               if (properties != null) {
-                       for (String key : properties.keySet()) {
-                               String property = key;
-                               if (property.startsWith(PROP_ARGEO_OSGI_START)) {
-                                       map.put(property, properties.get(property));
-                               }
-                       }
-               }
+//             if (properties != null) {
+//                     for (String key : properties.keySet()) {
+//                             String property = key;
+//                             if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
+//                                     map.put(property, properties.get(property));
+//                             }
+//                     }
+//             }
                // then try all start level until a maximum
-               int maxStartLevel = Integer.parseInt(getProperty(PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL));
+               int maxStartLevel = Integer
+                               .parseInt(getProperty(InitConstants.PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL));
                for (int i = 1; i <= maxStartLevel; i++) {
-                       String key = PROP_ARGEO_OSGI_START + "." + i;
+                       String key = InitConstants.PROP_ARGEO_OSGI_START + "." + i;
                        String value = getProperty(key);
                        if (value != null)
                                map.put(key, value);
 
                }
                // finally, override with system properties
-               for (Object key : System.getProperties().keySet()) {
-                       if (key.toString().startsWith(PROP_ARGEO_OSGI_START)) {
-                               map.put(key.toString(), System.getProperty(key.toString()));
-                       }
-               }
+//             for (Object key : System.getProperties().keySet()) {
+//                     if (key.toString().startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
+//                             map.put(key.toString(), System.getProperty(key.toString()));
+//                     }
+//             }
                // start
                doStartBundles(map);
        }
 
-       @Deprecated
-       public void startBundles(Properties properties) {
-               Map<String, String> map = new TreeMap<>();
-               // first use properties
-               if (properties != null) {
-                       for (Object key : properties.keySet()) {
-                               String property = key.toString();
-                               if (property.startsWith(PROP_ARGEO_OSGI_START)) {
-                                       map.put(property, properties.get(property).toString());
-                               }
-                       }
-               }
-               startBundles(map);
-       }
+//     void startBundles(Properties properties) {
+//             Map<String, String> map = new TreeMap<>();
+//             // first use properties
+//             if (properties != null) {
+//                     for (Object key : properties.keySet()) {
+//                             String property = key.toString();
+//                             if (property.startsWith(InitConstants.PROP_ARGEO_OSGI_START)) {
+//                                     map.put(property, properties.get(property).toString());
+//                             }
+//                     }
+//             }
+//             startBundles(map);
+//     }
 
-       /** Start bundle based on keys starting with {@link #PROP_ARGEO_OSGI_START}. */
+       /**
+        * Start bundle based on keys starting with
+        * {@link InitConstants#PROP_ARGEO_OSGI_START}.
+        */
        protected void doStartBundles(Map<String, String> properties) {
                FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
 
                // default and active start levels from System properties
                int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
-               int defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
-               int activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6"));
-               if (OsgiBootUtils.isDebug()) {
-                       OsgiBootUtils.debug("OSGi default start level: "
-                                       + getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "<not set>") + ", using " + defaultStartLevel);
-                       OsgiBootUtils.debug("OSGi active start level: " + getProperty(PROP_OSGI_STARTLEVEL, "<not set>")
+               int defaultStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
+               int activeStartLevel = Integer.parseInt(getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "6"));
+               if (logger.isLoggable(TRACE)) {
+                       logger.log(TRACE,
+                                       "OSGi default start level: "
+                                                       + getProperty(InitConstants.PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "<not set>") + ", using "
+                                                       + defaultStartLevel);
+                       logger.log(TRACE, "OSGi active start level: " + getProperty(InitConstants.PROP_OSGI_STARTLEVEL, "<not set>")
                                        + ", using " + activeStartLevel);
-                       OsgiBootUtils.debug("Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: "
+                       logger.log(TRACE, "Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: "
                                        + initialStartLevel + ")");
                }
 
@@ -407,23 +393,20 @@ public class OsgiBoot implements OsgiBootConstants {
                                        try {
                                                bundle.start();
                                        } catch (BundleException e) {
-                                               OsgiBootUtils.error("Cannot mark " + bsn + " as started", e);
+                                               logger.log(ERROR, "Cannot mark " + bsn + " as started", e);
                                        }
-                                       if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.debug(bsn + " v" + bundle.getVersion() + " starts at level " + level);
+                                       logger.log(TRACE, () -> bsn + " v" + bundle.getVersion() + " starts at level " + level);
                                }
                        }
                }
 
-               if (OsgiBootUtils.isDebug())
-                       OsgiBootUtils.debug("About to set framework start level to " + activeStartLevel + " ...");
+               logger.log(TRACE, () -> "About to set framework start level to " + activeStartLevel + " ...");
 
                frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> {
                        if (event.getType() == FrameworkEvent.ERROR) {
-                               OsgiBootUtils.error("Start sequence failed", event.getThrowable());
+                               logger.log(ERROR, "Start sequence failed", event.getThrowable());
                        } else {
-                               if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.debug("Framework started at level " + frameworkStartLevel.getStartLevel());
+                               logger.log(TRACE, () -> "Framework started at level " + frameworkStartLevel.getStartLevel());
                        }
                });
 
@@ -454,11 +437,12 @@ public class OsgiBoot implements OsgiBootConstants {
                        Integer defaultStartLevel) {
 
                // default (and previously, only behaviour)
-               appendToStartLevels(startLevels, defaultStartLevel, properties.getOrDefault(PROP_ARGEO_OSGI_START, ""));
+               appendToStartLevels(startLevels, defaultStartLevel,
+                               properties.getOrDefault(InitConstants.PROP_ARGEO_OSGI_START, ""));
 
                // list argeo.osgi.start.* system properties
                Iterator<String> keys = properties.keySet().iterator();
-               final String prefix = PROP_ARGEO_OSGI_START + ".";
+               final String prefix = InitConstants.PROP_ARGEO_OSGI_START + ".";
                while (keys.hasNext()) {
                        String key = keys.next();
                        if (key.startsWith(prefix)) {
@@ -529,8 +513,7 @@ public class OsgiBoot implements OsgiBootConstants {
                        return urls;
 
 //             bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns);
-               if (OsgiBootUtils.isDebug())
-                       debug(PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
+               logger.log(TRACE, () -> PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
 
                StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
                List<BundlesSet> bundlesSets = new ArrayList<BundlesSet>();
@@ -632,8 +615,8 @@ public class OsgiBoot implements OsgiBootConstants {
                        File[] files = baseDir.listFiles();
 
                        if (files == null) {
-                               if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.warn("Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
+                               if (logger.isLoggable(TRACE))
+                                       logger.log(Level.WARNING, "Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
                                                        + ", isDirectory=" + baseDir.isDirectory());
                                return;
                        }
@@ -671,8 +654,8 @@ public class OsgiBoot implements OsgiBootConstants {
 //                                                     }
                                                } else {
                                                        boolean nonDirectoryOk = matcher.matches(Paths.get(newCurrentPath));
-                                                       if (OsgiBootUtils.isDebug())
-                                                               debug(currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
+                                                       logger.log(TRACE,
+                                                                       () -> currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
                                                        if (nonDirectoryOk)
                                                                matched.add(relativeToFullPath(base, newCurrentPath));
                                                }
@@ -721,7 +704,7 @@ public class OsgiBoot implements OsgiBootConstants {
                return (basePath + '/' + relativePath).replace('/', File.separatorChar);
        }
 
-       private void refreshFramework() {
+       public void refresh() {
                Bundle systemBundle = bundleContext.getBundle(0);
                FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
                // TODO deal with refresh breaking native loading (e.g SWT)
@@ -753,4 +736,47 @@ public class OsgiBoot implements OsgiBootConstants {
                return bundleContext;
        }
 
+       /*
+        * PLAIN OSGI LAUNCHER
+        */
+       /** Launch an OSGi framework. OSGi Boot initialisation is NOT performed. */
+       public static Framework defaultOsgiLaunch(Map<String, String> configuration) {
+               Optional<FrameworkFactory> frameworkFactory = ServiceLoader.load(FrameworkFactory.class).findFirst();
+               if (frameworkFactory.isEmpty())
+                       throw new IllegalStateException("No framework factory found");
+               return defaultOsgiLaunch(frameworkFactory.get(), configuration);
+       }
+
+       /** Launch an OSGi framework. OSGi Boot initialisation is NOT performed. */
+       public static Framework defaultOsgiLaunch(FrameworkFactory frameworkFactory, Map<String, String> configuration) {
+               // start OSGi
+               Framework framework = frameworkFactory.newFramework(configuration);
+               try {
+                       framework.start();
+               } catch (BundleException e) {
+                       throw new IllegalStateException("Cannot start OSGi framework", e);
+               }
+               return framework;
+       }
+
+       /*
+        * OSGI UTILITIES
+        */
+       /** Uninstall all bundles with these symbolic names */
+       public static void uninstallBundles(BundleContext bc, String... symbolicNames) {
+               List<String> lst = Arrays.asList(symbolicNames);
+               for (Bundle b : bc.getBundles()) {
+                       String sn = b.getSymbolicName();
+                       if (sn == null)
+                               continue;
+                       if (lst.contains(sn)) {
+                               try {
+                                       b.uninstall();
+                               } catch (BundleException e) {
+                                       logger.log(ERROR, "Cannot uninstall " + sn, e);
+                               }
+                       }
+               }
+
+       }
 }
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootConstants.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootConstants.java
deleted file mode 100644 (file)
index e45f826..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.init.osgi;
-
-public interface OsgiBootConstants {
-
-}
index b56b6b070abe7908df3059327573a90d5f54af0b..074ecedb6b6dfd3c0b85451f84e4eee36953325b 100644 (file)
@@ -1,5 +1,8 @@
 package org.argeo.init.osgi;
 
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.lang.System.Logger;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -14,8 +17,9 @@ import org.osgi.framework.ServiceReference;
 import org.osgi.service.packageadmin.ExportedPackage;
 import org.osgi.service.packageadmin.PackageAdmin;
 
-@SuppressWarnings("deprecation")
 class OsgiBootDiagnostics {
+       private final static Logger logger = System.getLogger(OsgiBootDiagnostics.class.getName());
+
        private final BundleContext bundleContext;
 
        public OsgiBootDiagnostics(BundleContext bundleContext) {
@@ -41,7 +45,7 @@ class OsgiBootDiagnostics {
                }
 
                if (unresolvedBundles.size() != 0) {
-                       OsgiBootUtils.warn("Unresolved bundles " + unresolvedBundles);
+                       logger.log(WARNING, "Unresolved bundles " + unresolvedBundles);
                }
 
                // try to start unresolved bundles in order to trigger diagnostics
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java
deleted file mode 100644 (file)
index a782ac3..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-package org.argeo.init.osgi;
-
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.ServiceLoader;
-import java.util.StringTokenizer;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.launch.Framework;
-import org.osgi.framework.launch.FrameworkFactory;
-
-/** Utilities, mostly related to logging. */
-public class OsgiBootUtils {
-       private final static Logger logger = System.getLogger(OsgiBootUtils.class.getName());
-
-       public static void info(Object obj) {
-               logger.log(Level.INFO, () -> Objects.toString(obj));
-       }
-
-       public static void debug(Object obj) {
-               logger.log(Level.TRACE, () -> Objects.toString(obj));
-       }
-
-       public static void warn(Object obj) {
-               logger.log(Level.WARNING, () -> Objects.toString(obj));
-       }
-
-       public static void error(Object obj, Throwable e) {
-               logger.log(Level.ERROR, () -> Objects.toString(obj), e);
-       }
-
-       public static boolean isDebug() {
-               return logger.isLoggable(Level.TRACE);
-       }
-
-       public static String stateAsString(int state) {
-               switch (state) {
-               case Bundle.UNINSTALLED:
-                       return "UNINSTALLED";
-               case Bundle.INSTALLED:
-                       return "INSTALLED";
-               case Bundle.RESOLVED:
-                       return "RESOLVED";
-               case Bundle.STARTING:
-                       return "STARTING";
-               case Bundle.ACTIVE:
-                       return "ACTIVE";
-               case Bundle.STOPPING:
-                       return "STOPPING";
-               default:
-                       return Integer.toString(state);
-               }
-       }
-
-       /**
-        * @return ==0: versions are identical, &lt;0: tested version is newer, &gt;0:
-        *         currentVersion is newer.
-        */
-       public static int compareVersions(String currentVersion, String testedVersion) {
-               List<String> cToks = new ArrayList<String>();
-               StringTokenizer cSt = new StringTokenizer(currentVersion, ".");
-               while (cSt.hasMoreTokens())
-                       cToks.add(cSt.nextToken());
-               List<String> tToks = new ArrayList<String>();
-               StringTokenizer tSt = new StringTokenizer(currentVersion, ".");
-               while (tSt.hasMoreTokens())
-                       tToks.add(tSt.nextToken());
-
-               int comp = 0;
-               comp: for (int i = 0; i < cToks.size(); i++) {
-                       if (tToks.size() <= i) {
-                               // equals until then, tested shorter
-                               comp = 1;
-                               break comp;
-                       }
-
-                       String c = (String) cToks.get(i);
-                       String t = (String) tToks.get(i);
-
-                       try {
-                               int cInt = Integer.parseInt(c);
-                               int tInt = Integer.parseInt(t);
-                               if (cInt == tInt)
-                                       continue comp;
-                               else {
-                                       comp = (cInt - tInt);
-                                       break comp;
-                               }
-                       } catch (NumberFormatException e) {
-                               if (c.equals(t))
-                                       continue comp;
-                               else {
-                                       comp = c.compareTo(t);
-                                       break comp;
-                               }
-                       }
-               }
-
-               if (comp == 0 && tToks.size() > cToks.size()) {
-                       // equals until then, current shorter
-                       comp = -1;
-               }
-
-               return comp;
-       }
-
-       public static Framework launch(Map<String, String> configuration) {
-               Optional<FrameworkFactory> frameworkFactory = ServiceLoader.load(FrameworkFactory.class).findFirst();
-               if (frameworkFactory.isEmpty())
-                       throw new IllegalStateException("No framework factory found");
-               return launch(frameworkFactory.get(), configuration);
-       }
-
-       /** Launch an OSGi framework. */
-       public static Framework launch(FrameworkFactory frameworkFactory, Map<String, String> configuration) {
-               // start OSGi
-               Framework framework = frameworkFactory.newFramework(configuration);
-               try {
-                       framework.start();
-               } catch (BundleException e) {
-                       throw new IllegalStateException("Cannot start OSGi framework", e);
-               }
-               return framework;
-       }
-
-       @Deprecated
-       public static Map<String, String> equinoxArgsToConfiguration(String[] args) {
-               // FIXME implement it
-               return new HashMap<>();
-       }
-
-}
index cd2c80acb556792558ba6f316362932e3898b08b..f1c16af91aa76110d59b5cbf764587df3f0bebe7 100644 (file)
@@ -1,5 +1,9 @@
 package org.argeo.init.osgi;
 
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.TRACE;
+
+import java.lang.System.Logger;
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.ArrayList;
@@ -8,10 +12,10 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Set;
 import java.util.TreeMap;
 
+import org.argeo.api.init.InitConstants;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
@@ -25,6 +29,8 @@ import org.osgi.util.tracker.ServiceTracker;
 
 /** OSGi builder, focusing on ease of use for scripting. */
 public class OsgiBuilder {
+       private final static Logger logger = System.getLogger(OsgiBuilder.class.getName());
+
        private final static String PROP_HTTP_PORT = "org.osgi.service.http.port";
        private final static String PROP_HTTPS_PORT = "org.osgi.service.https.port";
        private final static String PROP_OSGI_CLEAN = "osgi.clean";
@@ -38,21 +44,23 @@ public class OsgiBuilder {
 
        public OsgiBuilder() {
                // configuration.put("osgi.clean", "true");
-               configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA));
-               configuration.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA));
+               configuration.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA,
+                               System.getProperty(InitConstants.PROP_OSGI_CONFIGURATION_AREA));
+               configuration.put(InitConstants.PROP_OSGI_INSTANCE_AREA,
+                               System.getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA));
                configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
        }
 
        public Framework launch() {
+               configuration.putAll(startLevelsToProperties());
                // start OSGi
-               framework = OsgiBootUtils.launch(configuration);
+               framework = OsgiBoot.defaultOsgiLaunch(configuration);
 
                BundleContext bc = framework.getBundleContext();
-               String osgiData = bc.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA);
+               String osgiData = bc.getProperty(InitConstants.PROP_OSGI_INSTANCE_AREA);
                // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
                String osgiConf = framework.getDataFile("").getAbsolutePath();
-               if (OsgiBootUtils.isDebug())
-                       OsgiBootUtils.debug("OSGi starting - data: " + osgiData + " conf: " + osgiConf);
+               logger.log(TRACE, () -> "OSGi starting - data: " + osgiData + " conf: " + osgiConf);
 
                OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
                if (distributionBundles.isEmpty()) {
@@ -65,7 +73,7 @@ public class OsgiBuilder {
                        }
                }
                // start bundles
-               osgiBoot.startBundles(startLevelsToProperties());
+               osgiBoot.startBundles();
 
                // if (OsgiBootUtils.isDebug())
                // for (Bundle bundle : bc.getBundles()) {
@@ -178,7 +186,7 @@ public class OsgiBuilder {
                try {
                        return st.waitForService(timeout);
                } catch (InterruptedException e) {
-                       OsgiBootUtils.error("Interrupted", e);
+                       logger.log(ERROR, "Interrupted", e);
                        return null;
                } finally {
                        st.close();
@@ -275,10 +283,10 @@ public class OsgiBuilder {
        //
        // UTILITIES
        //
-       private Properties startLevelsToProperties() {
-               Properties properties = new Properties();
+       private Map<String, String> startLevelsToProperties() {
+               Map<String, String> properties = new HashMap<>();
                for (Integer startLevel : startLevels.keySet()) {
-                       String property = OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel;
+                       String property = InitConstants.PROP_ARGEO_OSGI_START + "." + startLevel;
                        StringBuilder value = new StringBuilder();
                        for (String bundle : startLevels.get(startLevel).getBundles()) {
                                value.append(bundle);
index 186577b4dba40dda3ee7169b0d2bb4c863489c10..2914be38a75eac6af5e787a6fcca3820ff226b19 100644 (file)
@@ -1,18 +1,19 @@
 package org.argeo.init.osgi;
 
+import java.io.Serializable;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
 import java.lang.System.LoggerFinder;
 import java.util.Collections;
 import java.util.Hashtable;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.concurrent.Flow;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
-import org.argeo.init.RuntimeContext;
-import org.argeo.init.logging.ThinLoggerFinder;
-import org.osgi.framework.Bundle;
+import org.argeo.api.init.RuntimeContext;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
@@ -21,9 +22,16 @@ import org.osgi.framework.launch.FrameworkFactory;
 
 /** An OSGi runtime context. */
 public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
+       private final static Logger logger = System.getLogger(OsgiRuntimeContext.class.getName());
+
+       private final static long STOP_FOR_UPDATE_TIMEOUT = 60 * 1000;
+       private final static long CLOSE_TIMEOUT = 60 * 1000;
+
+       // private final static String SYMBOLIC_NAME_FELIX_SCR = "org.apache.felix.scr";
+
        private Map<String, String> config;
        private Framework framework;
-       private OsgiBoot osgiBoot;
+//     private OsgiBoot osgiBoot;
 
        /**
         * Constructor to use when the runtime context will create the OSGi
@@ -43,11 +51,17 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
 
        @Override
        public void run() {
-               ServiceLoader<FrameworkFactory> sl = ServiceLoader.load(FrameworkFactory.class);
-               Optional<FrameworkFactory> opt = sl.findFirst();
-               if (opt.isEmpty())
-                       throw new IllegalStateException("Cannot find OSGi framework");
-               framework = opt.get().newFramework(config);
+               if (framework != null && framework.getState() >= Framework.STARTING)
+                       throw new IllegalStateException("OSGi framework is already started");
+
+               if (framework == null) {
+                       ServiceLoader<FrameworkFactory> sl = ServiceLoader.load(FrameworkFactory.class);
+                       Optional<FrameworkFactory> opt = sl.findFirst();
+                       if (opt.isEmpty())
+                               throw new IllegalStateException("Cannot find OSGi framework");
+                       framework = opt.get().newFramework(config);
+               }
+
                try {
                        framework.start();
                        BundleContext bundleContext = framework.getBundleContext();
@@ -57,31 +71,75 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
                }
        }
 
-       public void start(BundleContext bundleContext) {
+       protected void start(BundleContext bundleContext) {
                // preferences
 //             SystemRootPreferences systemRootPreferences = ThinPreferencesFactory.getInstance().getSystemRootPreferences();
 //             bundleContext.registerService(AbstractPreferences.class, systemRootPreferences, new Hashtable<>());
 
                // Make sure LoggerFinder has been searched for, since it is lazily loaded
-               LoggerFinder.getLoggerFinder();
-
-               // logging
-               bundleContext.registerService(Consumer.class, ThinLoggerFinder.getConfigurationConsumer(),
-                               new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
-               bundleContext.registerService(Flow.Publisher.class, ThinLoggerFinder.getLogEntryPublisher(),
-                               new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
+               LoggerFinder loggerFinder = LoggerFinder.getLoggerFinder();
+
+               if (loggerFinder instanceof Consumer<?> && loggerFinder instanceof Supplier<?>) {
+                       @SuppressWarnings("unchecked")
+                       Consumer<Map<String, Object>> consumer = (Consumer<Map<String, Object>>) loggerFinder;
+                       // ThinLoggerFinder.getConfigurationConsumer()
+                       // ThinLoggerFinder.getLogEntryPublisher()
+
+                       @SuppressWarnings("unchecked")
+                       Supplier<Flow.Publisher<Map<String, Serializable>>> supplier = (Supplier<Flow.Publisher<Map<String, Serializable>>>) loggerFinder;
+                       // logging
+                       bundleContext.registerService(Consumer.class, consumer,
+                                       new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
+                       bundleContext.registerService(Flow.Publisher.class, supplier.get(),
+                                       new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
+               }
+               OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
+               String frameworkUuuid = bundleContext.getProperty(Constants.FRAMEWORK_UUID);
+
+               // separate thread in order to improve logging
+               Thread osgiBootThread = new Thread("OSGi boot framework " + frameworkUuuid) {
+                       @Override
+                       public void run() {
+                               osgiBoot.bootstrap();
+                       }
+               };
+               osgiBootThread.start();
+               // TODO return a completable stage so that inits can run in parallel
+//             try {
+//                     osgiBootThread.join(60 * 1000);
+//             } catch (InterruptedException e) {
+//                     // silent
+//             }
+       }
 
-               osgiBoot = new OsgiBoot(bundleContext);
-               osgiBoot.bootstrap(config);
+       public void update() {
+               stop();
+               try {
+                       waitForStop(STOP_FOR_UPDATE_TIMEOUT);
+               } catch (InterruptedException e) {
+                       logger.log(Level.TRACE, "Wait for stop interrupted", e);
+               }
+               run();
 
+               // TODO Optimise with OSGi mechanisms (e.g. framework.update())
+//             if (osgiBoot != null) {
+//                     Objects.requireNonNull(osgiBoot);
+//                     osgiBoot.update();
+//             }
        }
 
-       public void update() {
-               Objects.requireNonNull(osgiBoot);
-               osgiBoot.update();
+       protected void stop() {
+               if (framework == null)
+                       return;
+               stop(framework.getBundleContext());
+               try {
+                       framework.stop();
+               } catch (BundleException e) {
+                       throw new IllegalStateException("Cannot stop OSGi framework", e);
+               }
        }
 
-       public void stop(BundleContext bundleContext) {
+       protected void stop(BundleContext bundleContext) {
 //             if (loggingConfigurationSr != null)
 //                     try {
 //                             loggingConfigurationSr.unregister();
@@ -99,26 +157,32 @@ public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
        @Override
        public void waitForStop(long timeout) throws InterruptedException {
                if (framework == null)
-                       throw new IllegalStateException("Framework is not initialised");
+                       return;
 
                framework.waitForStop(timeout);
        }
 
        public void close() throws Exception {
+               if (framework == null)
+                       return;
                // TODO make shutdown of dynamic service more robust
-               Bundle scrBundle = osgiBoot.getBundlesBySymbolicName().get("org.apache.felix.scr");
-               if (scrBundle != null) {
-                       scrBundle.stop();
-                       while (!(scrBundle.getState() <= Bundle.RESOLVED)) {
-                               Thread.sleep(500);
-                       }
-                       Thread.sleep(1000);
-               }
-
-               stop(framework.getBundleContext());
-               if (framework != null)
-                       framework.stop();
+//             for (Bundle scrBundle : framework.getBundleContext().getBundles()) {
+//                     if (scrBundle.getSymbolicName().equals(SYMBOLIC_NAME_FELIX_SCR)) {
+//                             if (scrBundle.getState() > Bundle.RESOLVED) {
+//                                     scrBundle.stop();
+//                                     while (!(scrBundle.getState() <= Bundle.RESOLVED)) {
+//                                             Thread.sleep(100);
+//                                     }
+//                                     Thread.sleep(500);
+//                             }
+//                     }
+//             }
 
+               stop();
+               waitForStop(CLOSE_TIMEOUT);
+               framework = null;
+//                     osgiBoot = null;
+               config.clear();
        }
 
        public Framework getFramework() {
diff --git a/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java b/org.argeo.init/src/org/argeo/init/osgi/SubFrameworkActivator.java
new file mode 100644 (file)
index 0000000..2aa4880
--- /dev/null
@@ -0,0 +1,353 @@
+package org.argeo.init.osgi;
+
+import static java.lang.System.Logger.Level.INFO;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.System.Logger;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.argeo.api.init.InitConstants;
+import org.argeo.api.init.RuntimeManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.connect.ConnectContent;
+import org.osgi.framework.connect.ConnectFrameworkFactory;
+import org.osgi.framework.connect.ConnectModule;
+import org.osgi.framework.connect.ModuleConnector;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class SubFrameworkActivator implements BundleActivator {
+       private final static Logger logger = System.getLogger(SubFrameworkActivator.class.getName());
+
+//     private final static String EQUINOX_FRAMEWORK_CLASS = "org.eclipse.osgi.launch.Equinox";
+       private final static String EQUINOX_FRAMEWORK_FACTORY_CLASS = "org.eclipse.osgi.launch.EquinoxFactory";
+
+//     private ClassLoader bundleClassLoader;
+//     private ClassLoader subFrameworkClassLoader;
+       private BundleContext foreignBundleContext;
+
+       private ConnectFrameworkFactory frameworkFactory;
+
+       private Map<UUID, Framework> subFrameworks = Collections.synchronizedMap(new HashMap<>());
+
+       private UUID foreignFrameworkUuid;
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+               this.foreignBundleContext = context;
+               foreignFrameworkUuid = UUID.fromString(foreignBundleContext.getProperty(Constants.FRAMEWORK_UUID));
+
+               try {
+//                     Bundle bundle = context.getBundle();
+//                     ClassLoader bundleClassLoader = bundle.adapt(BundleWiring.class).getClassLoader();
+//                     subFrameworkClassLoader = new URLClassLoader(new URL[0], bundleClassLoader);
+
+                       @SuppressWarnings("unchecked")
+                       Class<? extends ConnectFrameworkFactory> frameworkFactoryClass = (Class<? extends ConnectFrameworkFactory>) Framework.class
+                                       .getClassLoader().loadClass(EQUINOX_FRAMEWORK_FACTORY_CLASS);
+                       frameworkFactory = frameworkFactoryClass.getConstructor().newInstance();
+
+                       boolean test = true;
+                       if (test)
+                               new Thread() {
+
+                                       @Override
+                                       public void run() {
+                                               for (int i = 0; i < 5; i++) {
+                                                       Map<String, String> config = new HashMap<>();
+                                                       Path basePase = Paths.get(System.getProperty("user.home"), ".config/argeo/test/",
+                                                                       "test" + i);
+                                                       config.put(InitConstants.PROP_OSGI_CONFIGURATION_AREA,
+                                                                       basePase.resolve(RuntimeManager.STATE).toString());
+                                                       config.put(InitConstants.PROP_OSGI_INSTANCE_AREA,
+                                                                       basePase.resolve(RuntimeManager.DATA).toString());
+                                                       config.put("argeo.host", "host" + i);
+                                                       config.put("osgi.console", "host" + i + ":2023");
+                                                       createFramework(config);
+                                               }
+                                       }
+
+                               }.start();
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw e;
+               }
+       }
+
+       Framework createFramework(Map<String, String> config) {
+               try {
+                       URL bundleConfigUrl = foreignBundleContext.getBundle().getEntry("config.ini");
+                       try (InputStream in = bundleConfigUrl.openStream()) {
+                               RuntimeManager.loadConfig(in, config);
+                       }
+
+                       // Equinox
+//                     config.put("osgi.frameworkParentClassloader", "current");
+//                     config.put("osgi.parentClassLoader", "app");
+//                     config.put("osgi.contextClassLoaderParent", "app");
+
+                       ModuleConnector moduleConnector = new ParentBundleModuleConnector(foreignBundleContext);
+
+//                     URL frameworkUrl = URI.create(bundleContext.getProperty("osgi.framework")).toURL();
+//                     URLClassLoader frameworkClassLoader = new URLClassLoader(new URL[] { frameworkUrl, });
+//                     Class<? extends Framework> frameworkClass = (Class<? extends Framework>) frameworkClassLoader
+//                                     .loadClass(EQUINOX_FRAMEWORK_CLASS);
+//                     Framework framework = frameworkClass.getConstructor(Map.class, ModuleConnector.class).newInstance(config,
+//                                     moduleConnector);
+
+                       config.put(InitConstants.PROP_ARGEO_OSGI_PARENT_UUID, foreignFrameworkUuid.toString());
+                       Framework framework = frameworkFactory.newFramework(config, moduleConnector);
+
+                       framework.init((e) -> {
+                               UUID frameworkUuid = UUID
+                                               .fromString(framework.getBundleContext().getProperty(Constants.FRAMEWORK_UUID));
+                               if (e.getType() == FrameworkEvent.STOPPED) {
+                                       subFrameworks.remove(frameworkUuid);
+                                       logger.log(INFO, "Removed subframework " + frameworkUuid + " in parent " + foreignFrameworkUuid);
+                               }
+                       });
+
+                       for (Bundle b : foreignBundleContext.getBundles()) {
+                               if (b.getBundleId() == 0)
+                                       continue;
+                               String location = b.getLocation();
+                               if (location.contains("/org.argeo.tp/") //
+                                               || location.contains("/org.argeo.tp.sys/") //
+                                               || location.contains("/org.argeo.tp.httpd/") //
+                                               || location.contains("/org.argeo.tp.sshd/") //
+                               ) {
+                                       framework.getBundleContext().installBundle(b.getLocation());
+                               }
+                       }
+
+                       OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
+                       osgiBoot.install();
+//                     OsgiBoot.uninstallBundles(osgiBoot.getBundleContext(), "org.argeo.api.cms");
+//                     OsgiBoot.uninstallBundles(osgiBoot.getBundleContext(), "org.osgi.service.useradmin");
+//                     osgiBoot.getBundleContext()
+//                                     .installBundle("initial@reference:file:../../../../../argeo-commons/org.argeo.api.cms/");
+//                     osgiBoot.getBundleContext().installBundle(
+//                                     "reference:file:/usr/local/share/a2/osgi/equinox/org.argeo.tp.osgi/org.osgi.service.useradmin.1.1.jar");
+                       osgiBoot.refresh();
+                       framework.start();
+                       osgiBoot.startBundles();
+
+//                     for (Bundle b : framework.getBundleContext().getBundles()) {
+//                             BundleContext bc = b.getBundleContext();
+//                             if (bc == null)
+//                                     System.err.println(b.getSymbolicName() + " BC null");
+//                     }
+
+                       UUID frameworkUuid = UUID.fromString(framework.getBundleContext().getProperty(Constants.FRAMEWORK_UUID));
+                       subFrameworks.put(frameworkUuid, framework);
+                       logger.log(INFO, "Created subframework " + frameworkUuid + " in parent " + foreignFrameworkUuid);
+                       return framework;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start framework", e);
+               }
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+               for (Iterator<Framework> it = subFrameworks.values().iterator(); it.hasNext();) {
+                       Framework framework = it.next();
+                       framework.stop();
+                       it.remove();
+
+               }
+//             for (Framework framework : subFrameworks.values()) {
+//                     framework.stop();
+//             }
+               subFrameworks.clear();
+               foreignBundleContext = null;
+               frameworkFactory = null;
+       }
+
+       static class ParentBundleModuleConnector implements ModuleConnector {
+               private final BundleContext foreignBundleContext;
+               private BundleContext localBundleContext;
+
+               public ParentBundleModuleConnector(BundleContext foreignBundleContext) {
+                       this.foreignBundleContext = foreignBundleContext;
+               }
+
+               @Override
+               public Optional<BundleActivator> newBundleActivator() {
+                       return Optional.of(new BundleActivator() {
+                               @Override
+                               public void start(BundleContext context) throws Exception {
+                                       ParentBundleModuleConnector.this.localBundleContext = context;
+                               }
+
+                               @Override
+                               public void stop(BundleContext context) throws Exception {
+                                       ParentBundleModuleConnector.this.localBundleContext = null;
+                               }
+
+                       });
+               }
+
+               @Override
+               public void initialize(File storage, Map<String, String> configuration) {
+               }
+
+               @Override
+               public Optional<ConnectModule> connect(String location) throws BundleException {
+                       Bundle bundle = foreignBundleContext.getBundle(location);
+                       if (bundle != null && bundle.getBundleId() != 0) {
+                               // System.out.println("Foreign Bundle: " + bundle.getSymbolicName() + " " +
+                               // location);
+                               ConnectModule module = new ConnectModule() {
+
+                                       @Override
+                                       public ConnectContent getContent() throws IOException {
+                                               return new ForeignBundleConnectContent(localBundleContext, bundle);
+                                       }
+                               };
+                               return Optional.of(module);
+                       }
+                       return Optional.empty();
+               }
+       }
+
+       static class ForeignBundleClassLoader extends ClassLoader {// implements BundleReference {
+               private BundleContext localBundleContext;
+               private Bundle foreignBundle;
+
+               public ForeignBundleClassLoader(BundleContext localBundleContext, Bundle foreignBundle) {
+                       super("Foreign bundle " + foreignBundle.toString(), Optional
+                                       .ofNullable(foreignBundle.adapt(BundleWiring.class)).map((bw) -> bw.getClassLoader()).orElse(null));
+                       this.localBundleContext = localBundleContext;
+                       this.foreignBundle = foreignBundle;
+               }
+
+//             @Override
+               protected Bundle getBundle() {
+                       return localBundleContext.getBundle(foreignBundle.getLocation());
+               }
+
+//             @Override
+//             public URL getResource(String resName) {
+//                     URL res = super.getResource(resName);
+//                     return res;
+//             }
+//
+//             @Override
+//             protected URL findResource(String resName) {
+//                     Bundle localBundle = getBundle();
+//                     if (localBundle != null) {
+//                             URL res = localBundle.getEntry(resName);
+//                             if (res != null)
+//                                     return res;
+//                     }
+//                     return null;
+//             }
+
+       }
+
+       static class ForeignBundleConnectContent implements ConnectContent {
+               private final Bundle foreignBundle;
+               private final ClassLoader classLoader;
+
+               public ForeignBundleConnectContent(BundleContext localBundleContext, Bundle foreignBundle) {
+                       this.foreignBundle = foreignBundle;
+                       this.classLoader = new ForeignBundleClassLoader(localBundleContext, foreignBundle);
+               }
+
+               @Override
+               public Optional<Map<String, String>> getHeaders() {
+                       Dictionary<String, String> dict = foreignBundle.getHeaders();
+                       List<String> keys = Collections.list(dict.keys());
+                       Map<String, String> dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), dict::get));
+                       return Optional.of(dictCopy);
+               }
+
+               @Override
+               public Iterable<String> getEntries() throws IOException {
+                       List<String> lst = Collections.list(foreignBundle.findEntries("", "*", true)).stream()
+                                       .map((u) -> u.getPath()).toList();
+                       return lst;
+               }
+
+               @Override
+               public Optional<ConnectEntry> getEntry(String path) {
+                       URL u = foreignBundle.getEntry(path);
+                       if (u == null) {
+                               u = foreignBundle.getEntry("bin/" + path);
+                               // System.err.println(u2);
+                       }
+                       if (u == null) {
+                               if ("plugin.xml".equals(path))
+                                       return Optional.empty();
+                               if (path.startsWith("META-INF/versions/"))
+                                       return Optional.empty();
+                               System.err.println(foreignBundle.getSymbolicName() + " " + path + " not found");
+                               return Optional.empty();
+                       }
+                       URL url = u;
+                       ConnectEntry urlConnectEntry = new ConnectEntry() {
+
+                               @Override
+                               public String getName() {
+                                       return path;
+                               }
+
+                               @Override
+                               public long getLastModified() {
+                                       return foreignBundle.getLastModified();
+                               }
+
+                               @Override
+                               public InputStream getInputStream() throws IOException {
+                                       return url.openStream();
+                               }
+
+                               @Override
+                               public long getContentLength() {
+                                       return -1;
+                               }
+                       };
+                       return Optional.of(urlConnectEntry);
+               }
+
+               @Override
+               public Optional<ClassLoader> getClassLoader() {
+                       ClassLoader cl;
+                       // cl = bundle.adapt(BundleWiring.class).getClassLoader();
+
+                       // cl = subFrameworkClassLoader;
+                       cl = classLoader;
+                       return Optional.of(cl);
+//                     return Optional.empty();
+               }
+
+               @Override
+               public void open() throws IOException {
+               }
+
+               @Override
+               public void close() throws IOException {
+
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/internal/init/InternalState.java b/org.argeo.init/src/org/argeo/internal/init/InternalState.java
new file mode 100644 (file)
index 0000000..03ab750
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.internal.init;
+
+import org.argeo.api.init.RuntimeContext;
+
+/**
+ * Keep track of the internal state mostly with static variables, typically in
+ * order to synchronise shutdown.
+ */
+public class InternalState {
+       private static RuntimeContext mainRuntimeContext;
+
+       /** The root runtime context in this JVM. */
+       public static RuntimeContext getMainRuntimeContext() {
+               return mainRuntimeContext;
+       }
+
+       public static void setMainRuntimeContext(RuntimeContext mainRuntimeContext) {
+               InternalState.mainRuntimeContext = mainRuntimeContext;
+       }
+
+}
index cabcc3462226b71849ca42301c21e05b63f150c2..979b11e352bda7d783c921a62e8cb5ed950a7564 160000 (submodule)
@@ -1 +1 @@
-Subproject commit cabcc3462226b71849ca42301c21e05b63f150c2
+Subproject commit 979b11e352bda7d783c921a62e8cb5ed950a7564
index c52ff887e5de4f3392d5e363feae9403ee674f61..aa23c9b5a0175728fccce17a7462e78044c09ec2 100644 (file)
@@ -1,7 +1,7 @@
 major=2
 minor=3
-micro=27
-qualifier=.next
+micro=28
+qualifier=
 
 Bundle-Copyright= \
 Copyright 2007-2023 Mathieu Baudier, \
index b6c2c1ae476b0f53aa434cc1ceb9814396821b3e..764a285b21b0e9b087cd495a25aeb4ccb1353415 100644 (file)
                        <arguments>
                        </arguments>
                </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
        </buildSpec>
        <natures>
                <nature>org.eclipse.pde.PluginNature</nature>
diff --git a/swt/org.argeo.swt.minidesktop/OSGI-INF/miniDesktop.xml b/swt/org.argeo.swt.minidesktop/OSGI-INF/miniDesktop.xml
new file mode 100644 (file)
index 0000000..51913c7
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="org.argeo.swt.minidesktop">
+   <implementation class="org.argeo.minidesktop.MiniDesktopManager"/>
+</scr:component>
index f3c13bec58e54fee4c8561e1cfec87b64e29ad74..1b43de5f74bd971c12081e6dbf500e9486c20a1e 100644 (file)
@@ -1,2 +1,5 @@
 Import-Package: org.eclipse.swt,\
 *
+
+Service-Component:\
+OSGI-INF/miniDesktop.xml,\
index 34d2e4d2dad529ceaeb953bfcdb63c51d69ffed2..04bd7e126fa8cf025c3292204f420db11a8d207f 100644 (file)
@@ -1,4 +1,5 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/miniDesktop.xml
 source.. = src/
 output.. = bin/
-bin.includes = META-INF/,\
-               .
index e7bb9e856d284e0cb6a8778ebc8ed016c4dabd4b..4bcb90441bebf92da060f88b99d0c7664162bf93 100644 (file)
@@ -78,11 +78,9 @@ public class MiniBrowser {
                browser.addLocationListener(new LocationAdapter() {
                        @Override
                        public void changed(LocationEvent event) {
-                               System.out.println(event);
                                if (addressT != null)
                                        addressT.setText(event.location);
                        }
-
                });
 
                MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserTitleListener(this, browser);
@@ -91,7 +89,6 @@ public class MiniBrowser {
        }
 
        public Browser openNewBrowserWindow() {
-
                if (isFullScreen()) {
                        // TODO manage multiple tabs?
                        return browser;
index adb2a55ba95f6393857c8a45877981204bed1642..6604b4f3e313a8371ebbdab3c39eb5e9b9fb79af 100644 (file)
@@ -44,82 +44,100 @@ public class MiniDesktopManager {
                this.stacking = stacking;
        }
 
+       public MiniDesktopManager() {
+               this(false, false);
+       }
+
+       public void start() {
+               init();
+       }
+
+       public void stop() {
+               dispose();
+               if (display != null) {
+                       display.dispose();
+                       display = null;
+               }
+       }
+
        public void init() {
                Display.setAppName("Mini SWT Desktop");
                display = Display.getCurrent();
                if (display != null)
                        throw new IllegalStateException("Already a display " + display);
-               display = new Display();
+               display = Display.getDefault();
 
-               if (display.getTouchEnabled()) {
-                       System.out.println("Touch enabled.");
-               }
-
-               images = new MiniDesktopImages(display);
+               display.syncExec(() -> {
+                       if (display.getTouchEnabled()) {
+                               System.out.println("Touch enabled.");
+                       }
 
-               int toolBarSize = 48;
+                       images = new MiniDesktopImages(display);
+
+                       int toolBarSize = 48;
+
+                       if (isFullscreen()) {
+                               rootShell = new Shell(display, SWT.NO_TRIM);
+                               rootShell.setFullScreen(true);
+                               Rectangle bounds = display.getBounds();
+                               rootShell.setLocation(0, 0);
+                               rootShell.setSize(bounds.width, bounds.height);
+                       } else {
+                               rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
+                               Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
+                               rootShell.setSize(shellArea.width, shellArea.height);
+                               rootShell.setText(Display.getAppName());
+                               rootShell.setImage(images.terminalIcon);
+                       }
 
-               if (isFullscreen()) {
-                       rootShell = new Shell(display, SWT.NO_TRIM);
-                       rootShell.setFullScreen(true);
-                       Rectangle bounds = display.getBounds();
-                       rootShell.setLocation(0, 0);
-                       rootShell.setSize(bounds.width, bounds.height);
-               } else {
-                       rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
-                       Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
-                       rootShell.setSize(shellArea.width, shellArea.height);
-                       rootShell.setText(Display.getAppName());
-                       rootShell.setImage(images.terminalIcon);
-               }
+                       rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
+                       Composite toolBarArea = new Composite(rootShell, SWT.NONE);
+                       toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
 
-               rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
-               Composite toolBarArea = new Composite(rootShell, SWT.NONE);
-               toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
-
-               ToolBar toolBar;
-               if (isFullscreen()) {
-                       toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
-                       toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
-                       createDock(toolBar);
-                       toolBarShell.pack();
-                       toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
-               } else {
-                       toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
-                       createDock(toolBar);
-                       toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
-               }
+                       ToolBar toolBar;
+                       if (isFullscreen()) {
+                               toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
+                               toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+                               createDock(toolBar);
+                               toolBarShell.pack();
+                               toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
+                       } else {
+                               toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+                               createDock(toolBar);
+                               toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
+                       }
 
-               if (isStacking()) {
-                       tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
-                       tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
-                       tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-                       Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
-                       tabFolder.setSelectionBackground(selectionBackground);
-
-                       // background
-                       Control background = createBackground(tabFolder);
-                       CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
-                       homeTabItem.setText("Home");
-                       homeTabItem.setImage(images.homeIcon);
-                       homeTabItem.setControl(background);
-                       tabFolder.setFocus();
-               } else {
-                       createBackground(rootShell);
-               }
+                       if (isStacking()) {
+                               tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
+                               tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
+                               tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+                               Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
+                               tabFolder.setSelectionBackground(selectionBackground);
+
+                               // background
+                               Control background = createBackground(tabFolder);
+                               CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
+                               homeTabItem.setText("Home");
+                               homeTabItem.setImage(images.homeIcon);
+                               homeTabItem.setControl(background);
+                               tabFolder.setFocus();
+                       } else {
+                               createBackground(rootShell);
+                       }
 
-               rootShell.open();
-               // rootShell.layout(true, true);
+                       rootShell.open();
+                       // rootShell.layout(true, true);
 
-               if (toolBarShell != null) {
-                       int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
-                       toolBarShell.setLocation(0, toolBarShellY);
-                       toolBarShell.open();
-               }
+                       if (toolBarShell != null) {
+                               int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
+                               toolBarShell.setLocation(0, toolBarShellY);
+                               toolBarShell.open();
+                       }
 
-               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-               System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
+                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+                       System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
+               });
        }
 
        protected void createDock(ToolBar toolBar) {
index 67fa5ceac2b37efd000f18e5032ea6e2e63f948b..c4e9ca74951b4238ab0a98fa5c150957e16b8573 100644 (file)
@@ -106,7 +106,7 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
                                return entryPoint;
                        }, properties);
                        if (log.isDebugEnabled())
-                               log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
+                               log.debug("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
                }
 //             if (log.isDebugEnabled())
 //                     log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
@@ -150,8 +150,7 @@ public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, Cm
                        rwtAppReg.unregister();
                if (bundleContext != null) {
                        rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
-                       if (log.isDebugEnabled())
-                               log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
+                       log.info("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
                }
        }
 
index 84ab27679b17451832d2af2fffcd2e7f98fef1b5..ae7bf49d79c92a8c38ca461aba6c7cea1e794f66 100644 (file)
@@ -1,9 +1,5 @@
 package org.argeo.eclipse.ui.specific;
 
-import org.eclipse.jface.viewers.AbstractTableViewer;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.swt.widgets.Widget;
 
@@ -27,14 +23,6 @@ public class EclipseUiSpecificUtils {
                widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
        }
 
-       /**
-        * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
-        */
-       public static void enableToolTipSupport(Viewer viewer) {
-               if (viewer instanceof ColumnViewer)
-                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
-       }
-
        private EclipseUiSpecificUtils() {
        }
 }
index 77aeae061fc780a80786477864f8a0391760fe18..7af1456b7dad68905d30986101ef50781a8033bd 100644 (file)
@@ -81,20 +81,24 @@ public class CmsRcpApp extends AbstractSwtCmsView implements CmsView {
                        // Styling
                        CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
                        if (theme != null) {
-                               cssEngine = new CSSSWTEngineImpl(display);
-                               for (String path : theme.getSwtCssPaths()) {
-                                       try (InputStream in = theme.loadPath(path)) {
-                                               cssEngine.parseStyleSheet(in);
-                                       } catch (IOException e) {
-                                               throw new IllegalStateException("Cannot load stylesheet " + path, e);
+                               try {
+                                       cssEngine = new CSSSWTEngineImpl(display);
+                                       for (String path : theme.getSwtCssPaths()) {
+                                               try (InputStream in = theme.loadPath(path)) {
+                                                       cssEngine.parseStyleSheet(in);
+                                               } catch (IOException e) {
+                                                       throw new IllegalStateException("Cannot load stylesheet " + path, e);
+                                               }
                                        }
+                                       cssEngine.setErrorHandler(new CSSErrorHandler() {
+                                               public void error(Exception e) {
+                                                       log.error("SWT styling error: ", e);
+                                               }
+                                       });
+                                       applyStyles(shell);
+                               } catch (Throwable e) {// could be a class not found error
+                                       log.error("Cannot initialise RCP theming", e);
                                }
-                               cssEngine.setErrorHandler(new CSSErrorHandler() {
-                                       public void error(Exception e) {
-                                               log.error("SWT styling error: ", e);
-                                       }
-                               });
-                               applyStyles(shell);
                        }
                        shell.layout(true, true);
 
index cd554de9d1ec09f63616a11dd1510b99ecd5bc67..96de08e10d6c5caedf23da78f819aa67567fe42f 100644 (file)
@@ -61,15 +61,18 @@ public class CmsRcpDisplayFactory {
                public void run() {
                        try {
                                display = Display.getDefault();
-                               display.setRuntimeExceptionHandler((e) -> e.printStackTrace());
-                               display.setErrorHandler((e) -> e.printStackTrace());
-
-                               while (!shutdown) {
-                                       if (!display.readAndDispatch())
-                                               display.sleep();
+                               boolean displayOwner = display.getThread() == this;
+                               if (displayOwner) {
+                                       display.setRuntimeExceptionHandler((e) -> e.printStackTrace());
+                                       display.setErrorHandler((e) -> e.printStackTrace());
+
+                                       while (!shutdown) {
+                                               if (!display.readAndDispatch())
+                                                       display.sleep();
+                                       }
+                                       display.dispose();
+                                       display = null;
                                }
-                               display.dispose();
-                               display = null;
                        } catch (UnsatisfiedLinkError e) {
                                logger.log(Level.ERROR,
                                                "Cannot load SWT, either because the SWT DLLs are no in the java.library.path,"
@@ -79,6 +82,7 @@ public class CmsRcpDisplayFactory {
                }
        }
 
+       @Deprecated
        public Display getDisplay() {
                return display;
        }
index bb88efda7c5a964dfc46a03aa4c7ca220acc8fed..23ea12bc1346a3d7340d0fa6954af79daf5a4fbb 100644 (file)
@@ -1,20 +1,18 @@
 Import-Package: \
-!java.*,\
-org.apache.commons.io,\
-org.eclipse.core.commands,\
+org.eclipse.core.commands;resolution:=optional,\
 !org.eclipse.core.runtime,\
 !org.eclipse.ui.plugin,\
 org.eclipse.swt,\
-javax.servlet.http;version="[3,5)",\
-javax.servlet;version="[3,5)",\
+javax.servlet.http;resolution:=optional,\
+javax.servlet;resolution:=optional,\
 *
 
 Export-Package: org.argeo.*,\
 org.eclipse.rap.fileupload.*;version="3.10",\
 org.eclipse.rap.rwt.*;version="3.10"
 
-# Was !org.eclipse.core.commands,\ why ?
+# Was !org.eclipse.core.commands, why ?
 
-#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin
+Bundle-Activator: org.argeo.internal.swt.specific.osgi.SwtSpecificRcpActivator
 #Bundle-ActivationPolicy: lazy
 #Ignore-Package: org.eclipse.core.commands
\ No newline at end of file
index 47ff35dc0e13ac6a6ac70f8482332c80445b873f..b99fd5815a3b61badc11205ee4586625ebb498c0 100644 (file)
@@ -1,13 +1,11 @@
 package org.argeo.eclipse.ui.rcp.internal.rwt;
 
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collections;
 import java.util.Map;
 import java.util.TreeMap;
 
-import org.argeo.cms.util.StreamUtils;
 import org.eclipse.rap.rwt.service.ResourceManager;
 
 public class RcpResourceManager implements ResourceManager {
@@ -15,11 +13,13 @@ public class RcpResourceManager implements ResourceManager {
 
        @Override
        public void register(String name, InputStream in) {
-               try {
-                       register.put(name, StreamUtils.toByteArray(in));
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot register " + name, e);
-               }
+               // FIXME implement it
+               throw new UnsupportedOperationException();
+//             try {
+//                     register.put(name, StreamUtils.toByteArray(in));
+//             } catch (IOException e) {
+//                     throw new RuntimeException("Cannot register " + name, e);
+//             }
        }
 
        @Override
index d1acbcfc026a39742ea8025bc972b8a625f5b0d5..11969b3eb4c7bc490ee374878d88bc5f3c617762 100644 (file)
@@ -1,8 +1,5 @@
 package org.argeo.eclipse.ui.specific;
 
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.swt.widgets.Widget;
 
 /** Static utilities to bridge differences between RCP and RAP */
@@ -25,16 +22,6 @@ public class EclipseUiSpecificUtils {
                // does nothing
        }
 
-       /**
-        * TootlTip support is supported for {@link ColumnViewer} in RCP
-        * 
-        * @see ColumnViewerToolTipSupport#enableFor(Viewer)
-        */
-       public static void enableToolTipSupport(Viewer viewer) {
-               if (viewer instanceof ColumnViewer)
-                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
-       }
-
        private EclipseUiSpecificUtils() {
        }
 }
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/internal/swt/specific/osgi/SwtSpecificRcpActivator.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/internal/swt/specific/osgi/SwtSpecificRcpActivator.java
new file mode 100644 (file)
index 0000000..e74e109
--- /dev/null
@@ -0,0 +1,75 @@
+package org.argeo.internal.swt.specific.osgi;
+
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class SwtSpecificRcpActivator implements BundleActivator {
+       private static Display display;
+       private static Shell rootShell;
+       private static UiThread uiThread;
+
+       private static boolean debug = true;
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+               if (display != null)
+                       throw new IllegalStateException("SWT display already exists");
+               uiThread = new UiThread();
+               uiThread.start();
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+               if (display == null)
+                       return; // TODO log issue
+               display.asyncExec(() -> rootShell.dispose());
+               display.wake();
+               uiThread.join(60 * 1000);
+               uiThread = null;
+               display = null;
+       }
+
+       static class UiThread extends Thread {
+
+               @Override
+               public void run() {
+                       boolean displayOwner = true;
+                       Display d = Display.getDefault();
+                       if (d.getThread() != UiThread.this) {
+                               displayOwner = false;
+                               // throw new IllegalStateException("There was already a default SWT display");
+                       }
+
+                       d.syncExec(() -> {
+                               try {
+                                       rootShell = new Shell(d);
+                               } catch (SWTError e) {
+                                       e.printStackTrace();
+                                       display.dispose();
+                                       return;
+                               }
+
+                               if (debug) {
+                                       rootShell.setLayout(new FillLayout());
+                                       new Label(rootShell, 0).setText("ROOT SHELL");
+                                       rootShell.pack();
+                                       rootShell.open();
+                               }
+                       });
+
+                       display = d;
+
+                       if (displayOwner) {
+                               while (!rootShell.isDisposed())
+                                       if (!display.readAndDispatch())
+                                               display.sleep();
+                               display.dispose();
+                       }
+               }
+       }
+}