From: Mathieu Baudier Date: Tue, 6 Feb 2018 12:21:38 +0000 (+0100) Subject: Introduce support for multiple JCR repositories. X-Git-Tag: argeo-commons-2.1.71~14 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=e61a7a2dbff5e17fbf1c6c8bbd7fa687935d2897 Introduce support for multiple JCR repositories. Framework properties override deploy config by clean (and not only by first init). --- diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java index e25ff7747..0a04e0804 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java @@ -45,11 +45,13 @@ import org.osgi.service.useradmin.UserAdmin; import org.osgi.util.tracker.ServiceTracker; public class CmsDeployment implements NodeDeployment { - private final static String LEGACY_JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; + // private final static String LEGACY_JCR_REPOSITORY_ALIAS = + // "argeo.jcr.repository.alias"; private final Log log = LogFactory.getLog(getClass()); private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + private DataModels dataModels; private DeployConfig deployConfig; private HomeRepository homeRepository; @@ -74,6 +76,7 @@ public class CmsDeployment implements NodeDeployment { cleanState = nodeState.isClean(); nodeHttp = new NodeHttp(); + dataModels = new DataModels(bc); initTrackers(); } @@ -110,7 +113,7 @@ public class CmsDeployment implements NodeDeployment { @Override public ConfigurationAdmin addingService(ServiceReference reference) { ConfigurationAdmin configurationAdmin = bc.getService(reference); - deployConfig = new DeployConfig(configurationAdmin, cleanState); + deployConfig = new DeployConfig(configurationAdmin, dataModels, cleanState); httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; try { // Configuration[] configs = configurationAdmin @@ -205,13 +208,13 @@ public class CmsDeployment implements NodeDeployment { } // home - prepareDataModel(KernelUtils.openAdminSession(deployedNodeRepository)); + prepareDataModel(NodeConstants.NODE, KernelUtils.openAdminSession(deployedNodeRepository)); } private void prepareHomeRepository(Repository deployedRepository) { Hashtable regProps = new Hashtable(); regProps.put(NodeConstants.CN, NodeConstants.HOME); - regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME); + // regProps.put(LEGACY_JCR_REPOSITORY_ALIAS, NodeConstants.HOME); homeRepository = new HomeRepository(deployedRepository); // register bc.registerService(Repository.class, homeRepository, regProps); @@ -232,36 +235,45 @@ public class CmsDeployment implements NodeDeployment { } /** Session is logged out. */ - private void prepareDataModel(Session adminSession) { + private void prepareDataModel(String cn, Session adminSession) { try { Set processed = new HashSet(); bundles: for (Bundle bundle : bc.getBundles()) { BundleWiring wiring = bundle.adapt(BundleWiring.class); if (wiring == null) continue bundles; - processWiring(adminSession, wiring, processed); + if (NodeConstants.NODE.equals(cn))// process all data models + processWiring(cn, adminSession, wiring, processed); + else { + List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + for (BundleCapability capability : capabilities) { + String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME); + if (dataModelName.equals(cn))// process only own data model + processWiring(cn, adminSession, wiring, processed); + } + } } } finally { JcrUtils.logoutQuietly(adminSession); } } - private void processWiring(Session adminSession, BundleWiring wiring, Set processed) { + private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set processed) { // recursively process requirements first List requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); for (BundleWire wire : requiredWires) { - processWiring(adminSession, wire.getProviderWiring(), processed); - // registerCnd(adminSession, wire.getCapability(), processed); + processWiring(cn, adminSession, wire.getProviderWiring(), processed); } List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); for (BundleCapability capability : capabilities) { - registerDataModelCapability(adminSession, capability, processed); + registerDataModelCapability(cn, adminSession, capability, processed); } } - private void registerDataModelCapability(Session adminSession, BundleCapability capability, Set processed) { + private void registerDataModelCapability(String cn, Session adminSession, BundleCapability capability, + Set processed) { Map attrs = capability.getAttributes(); - String name = (String) attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE); + String name = (String) attrs.get(DataModelNamespace.NAME); if (processed.contains(name)) { if (log.isTraceEnabled()) log.trace("Data model " + name + " has already been processed"); @@ -269,7 +281,7 @@ public class CmsDeployment implements NodeDeployment { } // CND - String path = (String) attrs.get(DataModelNamespace.CAPABILITY_CND_ATTRIBUTE); + String path = (String) attrs.get(DataModelNamespace.CND); if (path != null) { File dataModel = bc.getBundle().getDataFile("dataModels/" + path); if (!dataModel.exists()) { @@ -289,9 +301,21 @@ public class CmsDeployment implements NodeDeployment { } } - if (!asBoolean((String) attrs.get(DataModelNamespace.CAPABILITY_ABSTRACT_ATTRIBUTE))) { + if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT))) + return; + // Non abstract + boolean isStandalone = deployConfig.isStandalone(name); + boolean publishLocalRepo; + if (isStandalone && name.equals(cn))// includes the node itself + publishLocalRepo = true; + else if (!isStandalone && cn.equals(NodeConstants.NODE)) + publishLocalRepo = true; + else + publishLocalRepo = false; + + if (publishLocalRepo) { Hashtable properties = new Hashtable<>(); - properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name); + // properties.put(LEGACY_JCR_REPOSITORY_ALIAS, name); properties.put(NodeConstants.CN, name); if (name.equals(NodeConstants.NODE)) properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); @@ -302,20 +326,6 @@ public class CmsDeployment implements NodeDeployment { } } - private boolean asBoolean(String value) { - if (value == null) - return false; - switch (value) { - case "true": - return true; - case "false": - return false; - default: - throw new CmsException("Unsupported value for attribute " + DataModelNamespace.CAPABILITY_ABSTRACT_ATTRIBUTE - + ": " + value); - } - } - @Override public Long getAvailableSince() { return availableSince; @@ -329,19 +339,20 @@ public class CmsDeployment implements NodeDeployment { @Override public RepositoryContext addingService(ServiceReference reference) { - RepositoryContext nodeRepo = bc.getService(reference); - Object cn = reference.getProperty(NodeConstants.CN); + RepositoryContext repoContext = bc.getService(reference); + String cn = (String) reference.getProperty(NodeConstants.CN); if (cn != null) { if (cn.equals(NodeConstants.NODE)) { - prepareNodeRepository(nodeRepo.getRepository()); - prepareHomeRepository(nodeRepo.getRepository()); + prepareNodeRepository(repoContext.getRepository()); + // TODO separate home repository + prepareHomeRepository(repoContext.getRepository()); nodeAvailable = true; checkReadiness(); } else { - // TODO standalone + prepareDataModel(cn, KernelUtils.openAdminSession(repoContext.getRepository())); } } - return nodeRepo; + return repoContext; } @Override diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java new file mode 100644 index 000000000..b758a3063 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java @@ -0,0 +1,163 @@ +package org.argeo.cms.internal.kernel; + +import static org.argeo.node.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.argeo.cms.CmsException; +import org.argeo.node.DataModelNamespace; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; + +class DataModels implements BundleListener { + private Map dataModels = new TreeMap<>(); + + public DataModels(BundleContext bc) { + for (Bundle bundle : bc.getBundles()) + processBundle(bundle); + bc.addBundleListener(this); + } + + public List getNonAbstractDataModels() { + List res = new ArrayList<>(); + for (String name : dataModels.keySet()) { + DataModel dataModel = dataModels.get(name); + if (!dataModel.isAbstract()) + res.add(dataModel); + } + // TODO reorder? + return res; + } + + @Override + public void bundleChanged(BundleEvent event) { + if (event.getType() == Bundle.RESOLVED) { + processBundle(event.getBundle()); + } else if (event.getType() == Bundle.UNINSTALLED) { + BundleWiring wiring = event.getBundle().adapt(BundleWiring.class); + List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + if (providedDataModels.size() == 0) + return; + for (BundleCapability bundleCapability : providedDataModels) { + dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME)); + } + } + + } + + protected void processBundle(Bundle bundle) { + BundleWiring wiring = bundle.adapt(BundleWiring.class); + List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + if (providedDataModels.size() == 0) + return; + List requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); + for (BundleCapability bundleCapability : providedDataModels) { + DataModel dataModel = new DataModel(bundleCapability, requiredDataModels); + dataModels.put(dataModel.getName(), dataModel); + } + } + + /** Return a negative depth if dataModel is required by ref, 0 otherwise. */ + static int required(DataModel ref, DataModel dataModel, int depth) { + for (DataModel dm : ref.getRequired()) { + if (dm.equals(dataModel))// found here + return depth - 1; + int d = required(dm, dataModel, depth - 1); + if (d != 0)// found deeper + return d; + } + return 0;// not found + } + + class DataModel { + private final String name; + private final boolean abstrct; + // private final boolean standalone; + private final String cnd; + private final List required; + + private DataModel(BundleCapability bundleCapability, List requiredDataModels) { + assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace()); + Map attrs = bundleCapability.getAttributes(); + name = (String) attrs.get(DataModelNamespace.NAME); + assert name != null; + abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)); + // standalone = KernelUtils.asBoolean((String) + // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE)); + cnd = (String) attrs.get(DataModelNamespace.CND); + List req = new ArrayList<>(); + for (BundleWire wire : requiredDataModels) { + String requiredDataModelName = (String) wire.getCapability().getAttributes() + .get(DataModelNamespace.NAME); + assert requiredDataModelName != null; + DataModel requiredDataModel = dataModels.get(requiredDataModelName); + if (requiredDataModel == null) + throw new CmsException("No required data model " + requiredDataModelName); + req.add(requiredDataModel); + } + required = Collections.unmodifiableList(req); + } + + public String getName() { + return name; + } + + public boolean isAbstract() { + return abstrct; + } + + // public boolean isStandalone() { + // return !isAbstract(); + // } + + public String getCnd() { + return cnd; + } + + public List getRequired() { + return required; + } + + // @Override + // public int compareTo(DataModel o) { + // if (equals(o)) + // return 0; + // int res = required(this, o, 0); + // if (res != 0) + // return res; + // // the other way round + // res = required(o, this, 0); + // if (res != 0) + // return -res; + // return 0; + // } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof DataModel) + return ((DataModel) obj).name.equals(name); + return false; + } + + @Override + public String toString() { + return "Data model " + name; + } + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java index a26189222..bff544ab2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Dictionary; import java.util.List; import java.util.SortedMap; @@ -23,6 +24,7 @@ import org.argeo.naming.AttributesDictionary; import org.argeo.naming.LdifParser; import org.argeo.naming.LdifWriter; import org.argeo.node.NodeConstants; +import org.argeo.osgi.useradmin.UserAdminConf; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.service.cm.Configuration; @@ -36,15 +38,19 @@ class DeployConfig implements ConfigurationListener { private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH); private SortedMap deployConfigs = new TreeMap<>(); + private final DataModels dataModels; - public DeployConfig(ConfigurationAdmin configurationAdmin, boolean isClean) { + public DeployConfig(ConfigurationAdmin configurationAdmin, DataModels dataModels, boolean isClean) { + this.dataModels = dataModels; // ConfigurationAdmin configurationAdmin = // bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); try { + boolean isFirstInit = false; if (!isInitialized()) { // first init + isFirstInit = true; firstInit(); } - init(configurationAdmin, isClean); + init(configurationAdmin, isClean, isFirstInit); } catch (IOException e) { throw new CmsException("Could not init deploy configs", e); } @@ -53,10 +59,11 @@ class DeployConfig implements ConfigurationListener { } private void firstInit() throws IOException { + log.info("## FIRST INIT ##"); Files.createDirectories(deployConfigPath.getParent()); - FirstInit firstInit = new FirstInit(); - FirstInit.prepareInstanceArea(); + // FirstInit firstInit = new FirstInit(); + InitUtils.prepareFirstInitInstanceArea(); if (!Files.exists(deployConfigPath)) deployConfigs = new TreeMap<>(); @@ -64,25 +71,56 @@ class DeployConfig implements ConfigurationListener { try (InputStream in = Files.newInputStream(deployConfigPath)) { deployConfigs = new LdifParser().read(in); } + save(); + } + private void setFromFrameworkProperties(boolean isFirstInit) { // node repository - Dictionary nodeConfig = firstInit + Dictionary nodeConfig = InitUtils .getNodeRepositoryConfig(getProps(NodeConstants.NODE_REPOS_FACTORY_PID, NodeConstants.NODE)); // node repository is mandatory putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, nodeConfig); - // user admin + // additional repositories + dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) { + if (NodeConstants.NODE.equals(dataModel.getName())) + continue dataModels; + Dictionary config = InitUtils.getRepositoryConfig(dataModel.getName(), + getProps(NodeConstants.NODE_REPOS_FACTORY_PID, NodeConstants.NODE)); + if (config.size() != 0) + putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config); + } - List> userDirectoryConfigs = firstInit.getUserDirectoryConfigs(); - for (int i = 0; i < userDirectoryConfigs.size(); i++) { - Dictionary userDirectoryConfig = userDirectoryConfigs.get(i); - String cn = Integer.toString(i); - userDirectoryConfig.put(NodeConstants.CN, cn); - putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig); + // user admin + List> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs(); + if (userDirectoryConfigs.size() != 0) { + List activeCns = new ArrayList<>(); + for (int i = 0; i < userDirectoryConfigs.size(); i++) { + Dictionary userDirectoryConfig = userDirectoryConfigs.get(i); + String cn = UserAdminConf.baseDnHash(userDirectoryConfig); + activeCns.add(cn); + userDirectoryConfig.put(NodeConstants.CN, cn); + putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig); + } + // disable others + LdapName userAdminFactoryName = serviceFactoryDn(NodeConstants.NODE_USER_ADMIN_PID); + for (LdapName name : deployConfigs.keySet()) { + if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) { + try { + Attributes attrs = deployConfigs.get(name); + String cn = name.getRdn(name.size() - 1).getValue().toString(); + if (!activeCns.contains(cn)) { + attrs.put(UserAdminConf.disabled.name(), "true"); + } + } catch (Exception e) { + throw new CmsException("Cannot disable user directory " + name, e); + } + } + } } // http server - Dictionary webServerConfig = firstInit + Dictionary webServerConfig = InitUtils .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT)); if (!webServerConfig.isEmpty()) putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig); @@ -90,12 +128,13 @@ class DeployConfig implements ConfigurationListener { save(); } - private void init(ConfigurationAdmin configurationAdmin, boolean isClean) throws IOException { + private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException { try (InputStream in = Files.newInputStream(deployConfigPath)) { deployConfigs = new LdifParser().read(in); } if (isClean) { + setFromFrameworkProperties(isFirstInit); for (LdapName dn : deployConfigs.keySet()) { Rdn lastRdn = dn.getRdn(dn.size() - 1); LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1); @@ -210,6 +249,10 @@ class DeployConfig implements ConfigurationListener { } } + boolean isStandalone(String dataModelName) { + return getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null; + } + /* * UTILITIES */ diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java deleted file mode 100644 index 98c2483cc..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java +++ /dev/null @@ -1,227 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.net.InetAddress; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyStore; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.List; - -import javax.security.auth.x500.X500Principal; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.argeo.cms.internal.http.HttpConstants; -import org.argeo.cms.internal.jcr.RepoConf; -import org.argeo.node.NodeConstants; -import org.argeo.osgi.useradmin.UserAdminConf; - -/** - * Interprets framework properties in order to generate the initial deploy - * configuration. - */ -class FirstInit { - private final static Log log = LogFactory.getLog(FirstInit.class); - - public FirstInit() { - log.info("## FIRST INIT ##"); - } - - /** Override the provided config with the framework properties */ - Dictionary getNodeRepositoryConfig(Dictionary provided) { - Dictionary props = provided != null ? provided : new Hashtable(); - for (RepoConf repoConf : RepoConf.values()) { - Object value = getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); - if (value != null) - props.put(repoConf.name(), value); - } - props.put(NodeConstants.CN, NodeConstants.NODE); - // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, NodeConstants.NODE); - return props; - } - - /** Override the provided config with the framework properties */ - Dictionary getHttpServerConfig(Dictionary provided) { - String httpPort = getFrameworkProp("org.osgi.service.http.port"); - String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); - /// TODO make it more generic - String httpHost = getFrameworkProp(HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.HTTP_HOST); - String httpsHost = getFrameworkProp(HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.HTTPS_HOST); - - final Hashtable props = new Hashtable(); - // try { - if (httpPort != null || httpsPort != null) { - if (httpPort != null) { - props.put(HttpConstants.HTTP_PORT, httpPort); - props.put(HttpConstants.HTTP_ENABLED, true); - } - if (httpsPort != null) { - props.put(HttpConstants.HTTPS_PORT, httpsPort); - props.put(HttpConstants.HTTPS_ENABLED, true); - Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH); - String keyStorePassword = getFrameworkProp( - HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.SSL_PASSWORD); - if (keyStorePassword == null) - keyStorePassword = "changeit"; - if (!Files.exists(keyStorePath)) - createSelfSignedKeyStore(keyStorePath, keyStorePassword); - props.put(HttpConstants.SSL_KEYSTORETYPE, "PKCS12"); - props.put(HttpConstants.SSL_KEYSTORE, keyStorePath.toString()); - props.put(HttpConstants.SSL_PASSWORD, keyStorePassword); - props.put(HttpConstants.SSL_WANTCLIENTAUTH, true); - } - if (httpHost != null) - props.put(HttpConstants.HTTP_HOST, httpHost); - if (httpsHost != null) - props.put(HttpConstants.HTTPS_HOST, httpHost); - - props.put(NodeConstants.CN, NodeConstants.DEFAULT); - } - return props; - } - - List> getUserDirectoryConfigs() { - List> res = new ArrayList<>(); - File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile(); - List uris = new ArrayList<>(); - - // node roles - String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI); - String baseNodeRoleDn = NodeConstants.ROLES_BASEDN; - if (nodeRolesUri == null) { - nodeRolesUri = baseNodeRoleDn + ".ldif"; - File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri); - if (!nodeRolesFile.exists()) - try { - FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn + ".ldif"), - nodeRolesFile); - } catch (IOException e) { - throw new CmsException("Cannot copy demo resource", e); - } - // nodeRolesUri = nodeRolesFile.toURI().toString(); - } - uris.add(nodeRolesUri); - - // Business roles - String userAdminUris = getFrameworkProp(NodeConstants.USERADMIN_URIS); - if (userAdminUris == null) { - String demoBaseDn = "dc=example,dc=com"; - userAdminUris = demoBaseDn + ".ldif"; - File businessRolesFile = new File(nodeBaseDir, userAdminUris); - if (!businessRolesFile.exists()) - try { - FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn + ".ldif"), - businessRolesFile); - } catch (IOException e) { - throw new CmsException("Cannot copy demo resource", e); - } - // userAdminUris = businessRolesFile.toURI().toString(); - log.warn("## DEV Using dummy base DN " + demoBaseDn); - // TODO downgrade security level - } - for (String userAdminUri : userAdminUris.split(" ")) - uris.add(userAdminUri); - - // Interprets URIs - for (String uri : uris) { - URI u; - try { - u = new URI(uri); - if (u.getPath() == null) - throw new CmsException("URI " + uri + " must have a path in order to determine base DN"); - if (u.getScheme() == null) { - if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../")) - u = new File(uri).getCanonicalFile().toURI(); - else if (!uri.contains("/")) { - // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri); - u = new URI(uri); - } else - throw new CmsException("Cannot interpret " + uri + " as an uri"); - } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) { - u = new File(u).getCanonicalFile().toURI(); - } - } catch (Exception e) { - throw new CmsException("Cannot interpret " + uri + " as an uri", e); - } - Dictionary properties = UserAdminConf.uriAsProperties(u.toString()); - res.add(properties); - } - - return res; - } - - /** - * Called before node initialisation, in order populate OSGi instance are with - * some files (typically LDIF, etc). - */ - static void prepareInstanceArea() { - String nodeInit = getFrameworkProp(NodeConstants.NODE_INIT); - if (nodeInit == null) - nodeInit = "../../init"; - if (nodeInit.startsWith("http")) { - // remoteFirstInit(nodeInit); - return; - } - - // TODO use java.nio.file - File initDir; - if (nodeInit.startsWith(".")) - initDir = KernelUtils.getExecutionDir(nodeInit); - else - initDir = new File(nodeInit); - // TODO also uncompress archives - if (initDir.exists()) - try { - FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() { - - @Override - public boolean accept(File pathname) { - if (pathname.getName().equals(".svn") || pathname.getName().equals(".git")) - return false; - return true; - } - }); - log.info("CMS initialized from " + initDir.getCanonicalPath()); - } catch (IOException e) { - throw new CmsException("Cannot initialize from " + initDir, e); - } - } - - private void createSelfSignedKeyStore(Path keyStorePath, String keyStorePassword) { - // for (Provider provider : Security.getProviders()) - // System.out.println(provider.getName()); - File keyStoreFile = keyStorePath.toFile(); - char[] ksPwd = keyStorePassword.toCharArray(); - char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length); - if (!keyStoreFile.exists()) { - try { - keyStoreFile.getParentFile().mkdirs(); - KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd); - PkiUtils.generateSelfSignedCertificate(keyStore, - new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"), - 1024, keyPwd); - PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore); - if (log.isDebugEnabled()) - log.debug("Created self-signed unsecure keystore " + keyStoreFile); - } catch (Exception e) { - if (keyStoreFile.length() == 0) - keyStoreFile.delete(); - log.error("Cannot create keystore " + keyStoreFile, e); - } - } else { - throw new CmsException("Keystore " + keyStorePath + " already exists"); - } - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java new file mode 100644 index 000000000..8a22b480f --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java @@ -0,0 +1,237 @@ +package org.argeo.cms.internal.kernel; + +import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; +import org.argeo.cms.internal.http.HttpConstants; +import org.argeo.cms.internal.jcr.RepoConf; +import org.argeo.node.NodeConstants; +import org.argeo.osgi.useradmin.UserAdminConf; + +/** + * Interprets framework properties in order to generate the initial deploy + * configuration. + */ +class InitUtils { + private final static Log log = LogFactory.getLog(InitUtils.class); + + /** Override the provided config with the framework properties */ + static Dictionary getNodeRepositoryConfig(Dictionary provided) { + Dictionary props = provided != null ? provided : new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); + if (value != null) + props.put(repoConf.name(), value); + } + props.put(NodeConstants.CN, NodeConstants.NODE); + return props; + } + + static Dictionary getRepositoryConfig(String dataModelName, Dictionary provided) { + if (dataModelName.equals(NodeConstants.NODE) || dataModelName.equals(NodeConstants.HOME)) + throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); + Dictionary props = provided != null ? provided : new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = getFrameworkProp( + NodeConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name()); + if (value != null) + props.put(repoConf.name(), value); + } + if (props.size() != 0) + props.put(NodeConstants.CN, dataModelName); + return props; + } + + /** Override the provided config with the framework properties */ + static Dictionary getHttpServerConfig(Dictionary provided) { + String httpPort = getFrameworkProp("org.osgi.service.http.port"); + String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure"); + /// TODO make it more generic + String httpHost = getFrameworkProp(HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.HTTP_HOST); + String httpsHost = getFrameworkProp(HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.HTTPS_HOST); + + final Hashtable props = new Hashtable(); + // try { + if (httpPort != null || httpsPort != null) { + if (httpPort != null) { + props.put(HttpConstants.HTTP_PORT, httpPort); + props.put(HttpConstants.HTTP_ENABLED, true); + } + if (httpsPort != null) { + props.put(HttpConstants.HTTPS_PORT, httpsPort); + props.put(HttpConstants.HTTPS_ENABLED, true); + Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH); + String keyStorePassword = getFrameworkProp( + HttpConstants.JETTY_PROPERTY_PREFIX + HttpConstants.SSL_PASSWORD); + if (keyStorePassword == null) + keyStorePassword = "changeit"; + if (!Files.exists(keyStorePath)) + createSelfSignedKeyStore(keyStorePath, keyStorePassword); + props.put(HttpConstants.SSL_KEYSTORETYPE, "PKCS12"); + props.put(HttpConstants.SSL_KEYSTORE, keyStorePath.toString()); + props.put(HttpConstants.SSL_PASSWORD, keyStorePassword); + props.put(HttpConstants.SSL_WANTCLIENTAUTH, true); + } + if (httpHost != null) + props.put(HttpConstants.HTTP_HOST, httpHost); + if (httpsHost != null) + props.put(HttpConstants.HTTPS_HOST, httpHost); + + props.put(NodeConstants.CN, NodeConstants.DEFAULT); + } + return props; + } + + static List> getUserDirectoryConfigs() { + List> res = new ArrayList<>(); + File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile(); + List uris = new ArrayList<>(); + + // node roles + String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI); + String baseNodeRoleDn = NodeConstants.ROLES_BASEDN; + if (nodeRolesUri == null) { + nodeRolesUri = baseNodeRoleDn + ".ldif"; + File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri); + if (!nodeRolesFile.exists()) + try { + FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"), + nodeRolesFile); + } catch (IOException e) { + throw new CmsException("Cannot copy demo resource", e); + } + // nodeRolesUri = nodeRolesFile.toURI().toString(); + } + uris.add(nodeRolesUri); + + // Business roles + String userAdminUris = getFrameworkProp(NodeConstants.USERADMIN_URIS); + if (userAdminUris == null) { + String demoBaseDn = "dc=example,dc=com"; + userAdminUris = demoBaseDn + ".ldif"; + File businessRolesFile = new File(nodeBaseDir, userAdminUris); + if (!businessRolesFile.exists()) + try { + FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"), + businessRolesFile); + } catch (IOException e) { + throw new CmsException("Cannot copy demo resource", e); + } + // userAdminUris = businessRolesFile.toURI().toString(); + log.warn("## DEV Using dummy base DN " + demoBaseDn); + // TODO downgrade security level + } + for (String userAdminUri : userAdminUris.split(" ")) + uris.add(userAdminUri); + + // Interprets URIs + for (String uri : uris) { + URI u; + try { + u = new URI(uri); + if (u.getPath() == null) + throw new CmsException("URI " + uri + " must have a path in order to determine base DN"); + if (u.getScheme() == null) { + if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../")) + u = new File(uri).getCanonicalFile().toURI(); + else if (!uri.contains("/")) { + // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri); + u = new URI(uri); + } else + throw new CmsException("Cannot interpret " + uri + " as an uri"); + } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) { + u = new File(u).getCanonicalFile().toURI(); + } + } catch (Exception e) { + throw new CmsException("Cannot interpret " + uri + " as an uri", e); + } + Dictionary properties = UserAdminConf.uriAsProperties(u.toString()); + res.add(properties); + } + + return res; + } + + /** + * Called before node initialisation, in order populate OSGi instance are with + * some files (typically LDIF, etc). + */ + static void prepareFirstInitInstanceArea() { + String nodeInit = getFrameworkProp(NodeConstants.NODE_INIT); + if (nodeInit == null) + nodeInit = "../../init"; + if (nodeInit.startsWith("http")) { + // remoteFirstInit(nodeInit); + return; + } + + // TODO use java.nio.file + File initDir; + if (nodeInit.startsWith(".")) + initDir = KernelUtils.getExecutionDir(nodeInit); + else + initDir = new File(nodeInit); + // TODO also uncompress archives + if (initDir.exists()) + try { + FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() { + + @Override + public boolean accept(File pathname) { + if (pathname.getName().equals(".svn") || pathname.getName().equals(".git")) + return false; + return true; + } + }); + log.info("CMS initialized from " + initDir.getCanonicalPath()); + } catch (IOException e) { + throw new CmsException("Cannot initialize from " + initDir, e); + } + } + + private static void createSelfSignedKeyStore(Path keyStorePath, String keyStorePassword) { + // for (Provider provider : Security.getProviders()) + // System.out.println(provider.getName()); + File keyStoreFile = keyStorePath.toFile(); + char[] ksPwd = keyStorePassword.toCharArray(); + char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length); + if (!keyStoreFile.exists()) { + try { + keyStoreFile.getParentFile().mkdirs(); + KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd); + PkiUtils.generateSelfSignedCertificate(keyStore, + new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"), + 1024, keyPwd); + PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore); + if (log.isDebugEnabled()) + log.debug("Created self-signed unsecure keystore " + keyStoreFile); + } catch (Exception e) { + if (keyStoreFile.length() == 0) + keyStoreFile.delete(); + log.error("Cannot create keystore " + keyStoreFile, e); + } + } else { + throw new CmsException("Keystore " + keyStorePath + " already exists"); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java index 63cb356d3..1d8140911 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -25,6 +25,7 @@ import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.argeo.cms.CmsException; +import org.argeo.node.DataModelNamespace; import org.argeo.node.NodeConstants; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -206,7 +207,7 @@ class KernelUtils implements KernelConstants { * @throws CmsException * if the related bundle is not active */ - public static BundleContext getBundleContext(Class clzz) { + static BundleContext getBundleContext(Class clzz) { Bundle bundle = FrameworkUtil.getBundle(clzz); BundleContext bc = bundle.getBundleContext(); if (bc == null) @@ -214,10 +215,24 @@ class KernelUtils implements KernelConstants { return bc; } - private static BundleContext getBundleContext() { + static BundleContext getBundleContext() { return getBundleContext(KernelUtils.class); } + static boolean asBoolean(String value) { + if (value == null) + return false; + switch (value) { + case "true": + return true; + case "false": + return false; + default: + throw new CmsException("Unsupported value for attribute " + DataModelNamespace.ABSTRACT + + ": " + value); + } + } + private static URI safeUri(String uri) { if (uri == null) throw new CmsException("URI cannot be null"); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java index 67c66e915..6211ccd37 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java @@ -15,7 +15,7 @@ class LocalRepository extends JcrRepositoryWrapper { public LocalRepository(Repository repository, BundleCapability dataModelCapability) { super(repository); Map attrs = dataModelCapability.getAttributes(); - cn = (String) attrs.get(DataModelNamespace.CAPABILITY_NAME_ATTRIBUTE); + cn = (String) attrs.get(DataModelNamespace.NAME); putDescriptor(NodeConstants.CN, cn); } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java index 56f2f5c17..95b1f07ad 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -54,6 +54,7 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory private final String userObjectClass, userBase, groupObjectClass, groupBase; private final boolean readOnly; + private final boolean disabled; private final URI uri; private UserAdmin externalRoles; @@ -108,6 +109,11 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly)); } else readOnly = new Boolean(readOnlyStr); + String disabledStr = UserAdminConf.disabled.getValue(properties); + if (disabledStr != null) + disabled = new Boolean(disabledStr); + else + disabled = false; } /** Returns the groups this user is a direct member of. */ @@ -438,6 +444,10 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory return readOnly; } + public boolean isDisabled() { + return disabled; + } + protected UserAdmin getExternalRoles() { return externalRoles; } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index d2054416b..f7a7c6e47 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -139,8 +139,11 @@ public class AggregatingUserAdmin implements UserAdmin { return systemRoles; List res = new ArrayList(1); for (LdapName baseDn : businessRoles.keySet()) { - if (name.startsWith(baseDn)) - res.add(businessRoles.get(baseDn)); + if (name.startsWith(baseDn)) { + AbstractUserDirectory ud = businessRoles.get(baseDn); + if (!ud.isDisabled()) + res.add(ud); + } } if (res.size() == 0) throw new UserDirectoryException("Cannot find user admin for " + name); diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java index f6a237bcc..51d18349b 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java @@ -42,7 +42,28 @@ class DigestUtils { return bytes; } - private DigestUtils() { + static String sha1str(String str) { + byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8)); + return encodeHexString(hash); } + final private static char[] hexArray = "0123456789abcdef".toCharArray(); + + /** + * From + * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to + * -a-hex-string-in-java + */ + public static String encodeHexString(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + private DigestUtils() { + } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java index b3ead140c..37d633920 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java @@ -13,6 +13,7 @@ import java.util.Map; import javax.naming.Context; import javax.naming.NamingException; +import javax.naming.ldap.LdapName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,6 +44,9 @@ public enum UserAdminConf { /** Read-only source */ readOnly(null), + /** Disabled source */ + disabled(null), + /** Authentication realm */ realm(null); @@ -144,6 +148,9 @@ public enum UserAdminConf { if (bDn.endsWith(".ldif")) bDn = bDn.substring(0, bDn.length() - ".ldif".length()); + // Normalize base DN as LDAP name + bDn = new LdapName(bDn).toString(); + String principal = null; String credentials = null; if (scheme != null) @@ -254,4 +261,15 @@ public enum UserAdminConf { return "dc=" + hostname; } } + + /** + * Hash the base DN in order to have a deterministic string to be used as a cn + * for the underlying user directory. + */ + public static String baseDnHash(Dictionary properties) { + String bDn = (String) properties.get(baseDn.name()); + if (bDn == null) + throw new UserDirectoryException("No baseDn in " + properties); + return DigestUtils.sha1str(bDn); + } } diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java index e5de73836..ff80c5ac8 100644 --- a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java +++ b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java @@ -6,18 +6,20 @@ import javax.transaction.xa.XAResource; /** Information about a user directory. */ public interface UserDirectory { /** The base DN of all entries in this user directory */ - public LdapName getBaseDn(); + LdapName getBaseDn(); /** The related {@link XAResource} */ - public XAResource getXaResource(); + XAResource getXaResource(); - public boolean isReadOnly(); + boolean isReadOnly(); - public String getUserObjectClass(); + boolean isDisabled(); - public String getUserBase(); + String getUserObjectClass(); - public String getGroupObjectClass(); + String getUserBase(); - public String getGroupBase(); + String getGroupObjectClass(); + + String getGroupBase(); } diff --git a/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java b/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java index 6da250dbf..58e4a645a 100644 --- a/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java +++ b/org.argeo.node.api/src/org/argeo/node/DataModelNamespace.java @@ -6,15 +6,10 @@ import org.osgi.resource.Namespace; public class DataModelNamespace extends Namespace { public static final String CMS_DATA_MODEL_NAMESPACE = "cms.datamodel"; - public static final String CAPABILITY_NAME_ATTRIBUTE = "name"; - public static final String CAPABILITY_CND_ATTRIBUTE = "cnd"; + public static final String NAME = "name"; + public static final String CND = "cnd"; /** If 'true', indicates that no repository should be published */ - public static final String CAPABILITY_ABSTRACT_ATTRIBUTE = "abstract"; - /** - * If 'true', indicates that code using this data model should be prepared - * to have it stored in a different JCR repository than the node - */ - public static final String CAPABILITY_STANDALONE_ATTRIBUTE = "standalone"; + public static final String ABSTRACT = "abstract"; private DataModelNamespace() { // empty diff --git a/org.argeo.node.api/src/org/argeo/node/NodeConstants.java b/org.argeo.node.api/src/org/argeo/node/NodeConstants.java index 22afe0065..75b7826c9 100644 --- a/org.argeo.node.api/src/org/argeo/node/NodeConstants.java +++ b/org.argeo.node.api/src/org/argeo/node/NodeConstants.java @@ -90,6 +90,8 @@ public interface NodeConstants { // Node /** Properties configuring the node repository */ String NODE_REPO_PROP_PREFIX = "argeo.node.repo."; + /** Additional standalone repositories, related to data models. */ + String NODE_REPOS_PROP_PREFIX = "argeo.node.repos."; // HTTP String HTTP_PORT = "org.osgi.service.http.port"; String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";