You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2018/01/09 19:52:06 UTC
[15/16] knox git commit: Merge branch 'master' into
KNOX-998-Package_Restructuring
Merge branch 'master' into KNOX-998-Package_Restructuring
# Conflicts:
# gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/e5fd0622
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/e5fd0622
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/e5fd0622
Branch: refs/heads/KNOX-998-Package_Restructuring
Commit: e5fd0622493a7e3c62811ea03ff7931c979dd87a
Parents: e766b3b 99e6a54
Author: Sandeep More <mo...@apache.org>
Authored: Tue Jan 9 14:25:16 2018 -0500
Committer: Sandeep More <mo...@apache.org>
Committed: Tue Jan 9 14:25:16 2018 -0500
----------------------------------------------------------------------
LICENSE | 40 ++++-
NOTICE | 4 +-
.../discovery/ambari/ServiceURLCreator.java | 32 ++++
.../discovery/ambari/ServiceURLFactory.java | 75 +++++++++
.../discovery/ambari/WebHdfsUrlCreator.java | 84 ++++++++++
.../discovery/ambari/AmbariClientCommon.java | 14 +-
.../discovery/ambari/AmbariCluster.java | 6 +-
.../ambari/AmbariConfigurationMonitor.java | 52 +++++--
.../ambari/AmbariDynamicServiceURLCreator.java | 4 +-
.../discovery/ambari/PropertyEqualsHandler.java | 20 ++-
.../ambari/ServiceURLPropertyConfig.java | 7 +-
.../ambari-service-discovery-url-mappings.xml | 24 +--
.../AmbariDynamicServiceURLCreatorTest.java | 116 +++++++++-----
gateway-release/src/assembly.xml | 1 +
gateway-server/pom.xml | 2 +-
.../filter/PortMappingHelperHandler.java | 2 +-
.../topology/impl/DefaultTopologyService.java | 40 +++--
.../DefaultRemoteConfigurationMonitor.java | 22 ++-
.../org/apache/knox/gateway/util/KnoxCLI.java | 153 ++++++++++++-------
.../ZooKeeperConfigurationMonitorTest.java | 17 ++-
.../apache/knox/gateway/util/KnoxCLITest.java | 16 ++
gateway-service-remoteconfig/pom.xml | 5 -
gateway-test-release/pom.xml | 72 ++++++++-
pom.xml | 32 +++-
24 files changed, 690 insertions(+), 150 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/knox/blob/e5fd0622/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariClientCommon.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariClientCommon.java
index 9e5dcb3,0000000..1314305
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariClientCommon.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariClientCommon.java
@@@ -1,102 -1,0 +1,108 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.discovery.ambari;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryConfig;
+
+import java.util.HashMap;
+import java.util.Map;
+
+class AmbariClientCommon {
+
+ static final String AMBARI_CLUSTERS_URI = "/api/v1/clusters";
+
+ static final String AMBARI_HOSTROLES_URI =
+ AMBARI_CLUSTERS_URI + "/%s/services?fields=components/host_components/HostRoles";
+
+ static final String AMBARI_SERVICECONFIGS_URI =
+ AMBARI_CLUSTERS_URI + "/%s/configurations/service_config_versions?is_current=true";
+
+ private static final AmbariServiceDiscoveryMessages log = MessagesFactory.get(AmbariServiceDiscoveryMessages.class);
+
+ private RESTInvoker restClient;
+
+
+ AmbariClientCommon(AliasService aliasService) {
+ this(new RESTInvoker(aliasService));
+ }
+
+
+ AmbariClientCommon(RESTInvoker restInvoker) {
+ this.restClient = restInvoker;
+ }
+
+
+
+ Map<String, Map<String, AmbariCluster.ServiceConfiguration>> getActiveServiceConfigurations(String clusterName,
+ ServiceDiscoveryConfig config) {
- return getActiveServiceConfigurations(config.getAddress(),
- clusterName,
- config.getUser(),
- config.getPasswordAlias());
++ Map<String, Map<String, AmbariCluster.ServiceConfiguration>> activeConfigs = null;
++
++ if (config != null) {
++ activeConfigs = getActiveServiceConfigurations(config.getAddress(),
++ clusterName,
++ config.getUser(),
++ config.getPasswordAlias());
++ }
++
++ return activeConfigs;
+ }
+
+
+ Map<String, Map<String, AmbariCluster.ServiceConfiguration>> getActiveServiceConfigurations(String discoveryAddress,
+ String clusterName,
+ String discoveryUser,
+ String discoveryPwdAlias) {
+ Map<String, Map<String, AmbariCluster.ServiceConfiguration>> serviceConfigurations = new HashMap<>();
+
+ String serviceConfigsURL = String.format("%s" + AMBARI_SERVICECONFIGS_URI, discoveryAddress, clusterName);
+
+ JSONObject serviceConfigsJSON = restClient.invoke(serviceConfigsURL, discoveryUser, discoveryPwdAlias);
+ if (serviceConfigsJSON != null) {
+ // Process the service configurations
+ JSONArray serviceConfigs = (JSONArray) serviceConfigsJSON.get("items");
+ for (Object serviceConfig : serviceConfigs) {
+ String serviceName = (String) ((JSONObject) serviceConfig).get("service_name");
+ JSONArray configurations = (JSONArray) ((JSONObject) serviceConfig).get("configurations");
+ for (Object configuration : configurations) {
+ String configType = (String) ((JSONObject) configuration).get("type");
+ String configVersion = String.valueOf(((JSONObject) configuration).get("version"));
+
+ Map<String, String> configProps = new HashMap<>();
+ JSONObject configProperties = (JSONObject) ((JSONObject) configuration).get("properties");
+ for (String propertyName : configProperties.keySet()) {
+ configProps.put(propertyName, String.valueOf(((JSONObject) configProperties).get(propertyName)));
+ }
+ if (!serviceConfigurations.containsKey(serviceName)) {
+ serviceConfigurations.put(serviceName, new HashMap<>());
+ }
+ serviceConfigurations.get(serviceName).put(configType,
+ new AmbariCluster.ServiceConfiguration(configType,
+ configVersion,
+ configProps));
+ }
+ }
+ }
+
+ return serviceConfigurations;
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/e5fd0622/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
index bcf3adc,0000000..9d3fa74
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariCluster.java
@@@ -1,120 -1,0 +1,120 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.discovery.ambari;
+
+import org.apache.knox.gateway.topology.discovery.ServiceDiscovery;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class AmbariCluster implements ServiceDiscovery.Cluster {
+
+ private String name = null;
+
- private AmbariDynamicServiceURLCreator urlCreator;
++ private ServiceURLFactory urlFactory;
+
+ private Map<String, Map<String, ServiceConfiguration>> serviceConfigurations = new HashMap<>();
+
+ private Map<String, AmbariComponent> components = null;
+
+
+ AmbariCluster(String name) {
+ this.name = name;
+ components = new HashMap<>();
- urlCreator = new AmbariDynamicServiceURLCreator(this);
++ urlFactory = ServiceURLFactory.newInstance(this);
+ }
+
+ void addServiceConfiguration(String serviceName, String configurationType, ServiceConfiguration serviceConfig) {
+ if (!serviceConfigurations.keySet().contains(serviceName)) {
+ serviceConfigurations.put(serviceName, new HashMap<>());
+ }
+ serviceConfigurations.get(serviceName).put(configurationType, serviceConfig);
+ }
+
+
+ void addComponent(AmbariComponent component) {
+ components.put(component.getName(), component);
+ }
+
+
+ ServiceConfiguration getServiceConfiguration(String serviceName, String configurationType) {
+ ServiceConfiguration sc = null;
+ Map<String, ServiceConfiguration> configs = serviceConfigurations.get(serviceName);
+ if (configs != null) {
+ sc = configs.get(configurationType);
+ }
+ return sc;
+ }
+
+
+ Map<String, Map<String, ServiceConfiguration>> getServiceConfigurations() {
+ return serviceConfigurations;
+ }
+
+
+ Map<String, AmbariComponent> getComponents() {
+ return components;
+ }
+
+
+ AmbariComponent getComponent(String name) {
+ return components.get(name);
+ }
+
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+
+ @Override
+ public List<String> getServiceURLs(String serviceName) {
+ List<String> urls = new ArrayList<>();
- urls.addAll(urlCreator.create(serviceName));
++ urls.addAll(urlFactory.create(serviceName));
+ return urls;
+ }
+
+
+ static class ServiceConfiguration {
+
+ private String type;
+ private String version;
+ private Map<String, String> props;
+
+ ServiceConfiguration(String type, String version, Map<String, String> properties) {
+ this.type = type;
+ this.version = version;
+ this.props = properties;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Map<String, String> getProperties() {
+ return props;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/e5fd0622/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariConfigurationMonitor.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariConfigurationMonitor.java
index c3aa27a,0000000..920b05c7
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariConfigurationMonitor.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariConfigurationMonitor.java
@@@ -1,525 -1,0 +1,559 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.discovery.ambari;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.topology.discovery.ClusterConfigurationMonitor;
+import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryConfig;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+
+class AmbariConfigurationMonitor implements ClusterConfigurationMonitor {
+
+ private static final String TYPE = "Ambari";
+
+ private static final String CLUSTERS_DATA_DIR_NAME = "clusters";
+
+ private static final String PERSISTED_FILE_COMMENT = "Generated File. Do Not Edit!";
+
+ private static final String PROP_CLUSTER_PREFIX = "cluster.";
+ private static final String PROP_CLUSTER_SOURCE = PROP_CLUSTER_PREFIX + "source";
+ private static final String PROP_CLUSTER_NAME = PROP_CLUSTER_PREFIX + "name";
+ private static final String PROP_CLUSTER_USER = PROP_CLUSTER_PREFIX + "user";
+ private static final String PROP_CLUSTER_ALIAS = PROP_CLUSTER_PREFIX + "pwd.alias";
+
+ static final String INTERVAL_PROPERTY_NAME = "org.apache.knox.gateway.topology.discovery.ambari.monitor.interval";
+
+
+ private static final AmbariServiceDiscoveryMessages log = MessagesFactory.get(AmbariServiceDiscoveryMessages.class);
+
+ // Ambari address
+ // clusterName -> ServiceDiscoveryConfig
+ //
+ Map<String, Map<String, ServiceDiscoveryConfig>> clusterMonitorConfigurations = new HashMap<>();
+
+ // Ambari address
+ // clusterName
+ // configType -> version
+ //
+ Map<String, Map<String, Map<String, String>>> ambariClusterConfigVersions = new HashMap<>();
+
+ ReadWriteLock configVersionsLock = new ReentrantReadWriteLock();
+
+ private List<ConfigurationChangeListener> changeListeners = new ArrayList<>();
+
+ private AmbariClientCommon ambariClient;
+
+ PollingConfigAnalyzer internalMonitor;
+
+ GatewayConfig gatewayConfig = null;
+
+ static String getType() {
+ return TYPE;
+ }
+
+ AmbariConfigurationMonitor(GatewayConfig config, AliasService aliasService) {
+ this.gatewayConfig = config;
+ this.ambariClient = new AmbariClientCommon(aliasService);
+ this.internalMonitor = new PollingConfigAnalyzer(this);
+
+ // Override the default polling interval if it has been configured
+ int interval = config.getClusterMonitorPollingInterval(getType());
+ if (interval > 0) {
+ setPollingInterval(interval);
+ }
+
+ init();
+ }
+
+ @Override
+ public void setPollingInterval(int interval) {
+ internalMonitor.setInterval(interval);
+ }
+
+ private void init() {
+ loadDiscoveryConfiguration();
+ loadClusterVersionData();
+ }
+
+ /**
+ * Load any previously-persisted service discovery configurations.
+ * This is necessary for checking previously-deployed topologies.
+ */
+ private void loadDiscoveryConfiguration() {
+ File persistenceDir = getPersistenceDir();
+ if (persistenceDir != null) {
+ Collection<File> persistedConfigs = FileUtils.listFiles(persistenceDir, new String[]{"conf"}, false);
+ for (File persisted : persistedConfigs) {
+ Properties props = new Properties();
++ FileInputStream in = null;
+ try {
- props.load(new FileInputStream(persisted));
++ in = new FileInputStream(persisted);
++ props.load(in);
+
+ addDiscoveryConfig(props.getProperty(PROP_CLUSTER_NAME), new ServiceDiscoveryConfig() {
+ public String getAddress() {
+ return props.getProperty(PROP_CLUSTER_SOURCE);
+ }
+
+ public String getUser() {
+ return props.getProperty(PROP_CLUSTER_USER);
+ }
+
+ public String getPasswordAlias() {
+ return props.getProperty(PROP_CLUSTER_ALIAS);
+ }
+ });
+ } catch (IOException e) {
+ log.failedToLoadClusterMonitorServiceDiscoveryConfig(getType(), e);
++ } finally {
++ if (in != null) {
++ try {
++ in.close();
++ } catch (IOException e) {
++ //
++ }
++ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Load any previously-persisted cluster configuration version records, so the monitor will check
+ * previously-deployed topologies against the current cluster configuration.
+ */
+ private void loadClusterVersionData() {
+ File persistenceDir = getPersistenceDir();
+ if (persistenceDir != null) {
- Collection<File> persistedConfigs = FileUtils.listFiles(getPersistenceDir(), new String[]{"ver"}, false);
++ Collection<File> persistedConfigs = FileUtils.listFiles(persistenceDir, new String[]{"ver"}, false);
+ for (File persisted : persistedConfigs) {
+ Properties props = new Properties();
++ FileInputStream in = null;
+ try {
- props.load(new FileInputStream(persisted));
++ in = new FileInputStream(persisted);
++ props.load(in);
+
+ String source = props.getProperty(PROP_CLUSTER_SOURCE);
+ String clusterName = props.getProperty(PROP_CLUSTER_NAME);
+
+ Map<String, String> configVersions = new HashMap<>();
+ for (String name : props.stringPropertyNames()) {
+ if (!name.startsWith(PROP_CLUSTER_PREFIX)) { // Ignore implementation-specific properties
+ configVersions.put(name, props.getProperty(name));
+ }
+ }
+
+ // Map the config versions to the cluster name
+ addClusterConfigVersions(source, clusterName, configVersions);
+
+ } catch (IOException e) {
+ log.failedToLoadClusterMonitorConfigVersions(getType(), e);
++ } finally {
++ if (in != null) {
++ try {
++ in.close();
++ } catch (IOException e) {
++ //
++ }
++ }
+ }
+ }
+ }
+ }
+
+ private void persistDiscoveryConfiguration(String clusterName, ServiceDiscoveryConfig sdc) {
+ File persistenceDir = getPersistenceDir();
+ if (persistenceDir != null) {
+
+ Properties props = new Properties();
+ props.setProperty(PROP_CLUSTER_NAME, clusterName);
+ props.setProperty(PROP_CLUSTER_SOURCE, sdc.getAddress());
+
+ String username = sdc.getUser();
+ if (username != null) {
+ props.setProperty(PROP_CLUSTER_USER, username);
+ }
+ String pwdAlias = sdc.getPasswordAlias();
+ if (pwdAlias != null) {
+ props.setProperty(PROP_CLUSTER_ALIAS, pwdAlias);
+ }
+
+ persist(props, getDiscoveryConfigPersistenceFile(sdc.getAddress(), clusterName));
+ }
+ }
+
+ private void persistClusterVersionData(String address, String clusterName, Map<String, String> configVersions) {
+ File persistenceDir = getPersistenceDir();
+ if (persistenceDir != null) {
+ Properties props = new Properties();
+ props.setProperty(PROP_CLUSTER_NAME, clusterName);
+ props.setProperty(PROP_CLUSTER_SOURCE, address);
+ for (String name : configVersions.keySet()) {
+ props.setProperty(name, configVersions.get(name));
+ }
+
+ persist(props, getConfigVersionsPersistenceFile(address, clusterName));
+ }
+ }
+
+ private void persist(Properties props, File dest) {
++ FileOutputStream out = null;
+ try {
- props.store(new FileOutputStream(dest), PERSISTED_FILE_COMMENT);
++ out = new FileOutputStream(dest);
++ props.store(out, PERSISTED_FILE_COMMENT);
++ out.flush();
+ } catch (Exception e) {
+ log.failedToPersistClusterMonitorData(getType(), dest.getAbsolutePath(), e);
++ } finally {
++ if (out != null) {
++ try {
++ out.close();
++ } catch (IOException e) {
++ //
++ }
++ }
+ }
+ }
+
+ private File getPersistenceDir() {
+ File persistenceDir = null;
+
+ File dataDir = new File(gatewayConfig.getGatewayDataDir());
+ if (dataDir.exists()) {
+ File clustersDir = new File(dataDir, CLUSTERS_DATA_DIR_NAME);
+ if (!clustersDir.exists()) {
+ clustersDir.mkdirs();
+ }
+ persistenceDir = clustersDir;
+ }
+
+ return persistenceDir;
+ }
+
+ private File getDiscoveryConfigPersistenceFile(String address, String clusterName) {
+ return getPersistenceFile(address, clusterName, "conf");
+ }
+
+ private File getConfigVersionsPersistenceFile(String address, String clusterName) {
+ return getPersistenceFile(address, clusterName, "ver");
+ }
+
+ private File getPersistenceFile(String address, String clusterName, String ext) {
+ String fileName = address.replace(":", "_").replace("/", "_") + "-" + clusterName + "." + ext;
+ return new File(getPersistenceDir(), fileName);
+ }
+
+ /**
+ * Add cluster configuration details to the monitor's in-memory record.
+ *
+ * @param address An Ambari instance address.
+ * @param clusterName The name of a cluster associated with the Ambari instance.
+ * @param configVersions A Map of configuration types and their corresponding versions.
+ */
+ private void addClusterConfigVersions(String address, String clusterName, Map<String, String> configVersions) {
+ configVersionsLock.writeLock().lock();
+ try {
+ ambariClusterConfigVersions.computeIfAbsent(address, k -> new HashMap<>())
+ .put(clusterName, configVersions);
+ } finally {
+ configVersionsLock.writeLock().unlock();
+ }
+ }
+
+ public void start() {
+ (new Thread(internalMonitor, "AmbariConfigurationMonitor")).start();
+ }
+
+ public void stop() {
+ internalMonitor.stop();
+ }
+
+ @Override
+ public void addListener(ConfigurationChangeListener listener) {
+ changeListeners.add(listener);
+ }
+
+ /**
+ * Add discovery configuration details for the specified cluster, so the monitor knows how to connect to check for
+ * changes.
+ *
+ * @param clusterName The name of the cluster.
+ * @param config The associated service discovery configuration.
+ */
+ void addDiscoveryConfig(String clusterName, ServiceDiscoveryConfig config) {
+ clusterMonitorConfigurations.computeIfAbsent(config.getAddress(), k -> new HashMap<>()).put(clusterName, config);
+ }
+
+
+ /**
+ * Get the service discovery configuration associated with the specified Ambari instance and cluster.
+ *
+ * @param address An Ambari instance address.
+ * @param clusterName The name of a cluster associated with the Ambari instance.
+ *
+ * @return The associated ServiceDiscoveryConfig object.
+ */
+ ServiceDiscoveryConfig getDiscoveryConfig(String address, String clusterName) {
+ ServiceDiscoveryConfig config = null;
+ if (clusterMonitorConfigurations.containsKey(address)) {
+ config = clusterMonitorConfigurations.get(address).get(clusterName);
+ }
+ return config;
+ }
+
+
+ /**
+ * Add cluster configuration data to the monitor, which it will use when determining if configuration has changed.
+ *
+ * @param cluster An AmbariCluster object.
+ * @param discoveryConfig The discovery configuration associated with the cluster.
+ */
+ void addClusterConfigVersions(AmbariCluster cluster, ServiceDiscoveryConfig discoveryConfig) {
+
+ String clusterName = cluster.getName();
+
+ // Register the cluster discovery configuration for the monitor connections
+ persistDiscoveryConfiguration(clusterName, discoveryConfig);
+ addDiscoveryConfig(clusterName, discoveryConfig);
+
+ // Build the set of configuration versions
+ Map<String, String> configVersions = new HashMap<>();
+ Map<String, Map<String, AmbariCluster.ServiceConfiguration>> serviceConfigs = cluster.getServiceConfigurations();
+ for (String serviceName : serviceConfigs.keySet()) {
+ Map<String, AmbariCluster.ServiceConfiguration> configTypeVersionMap = serviceConfigs.get(serviceName);
+ for (AmbariCluster.ServiceConfiguration config : configTypeVersionMap.values()) {
+ String configType = config.getType();
+ String version = config.getVersion();
+ configVersions.put(configType, version);
+ }
+ }
+
+ persistClusterVersionData(discoveryConfig.getAddress(), clusterName, configVersions);
+ addClusterConfigVersions(discoveryConfig.getAddress(), clusterName, configVersions);
+ }
+
+
+ /**
+ * Remove the configuration record for the specified Ambari instance and cluster name.
+ *
+ * @param address An Ambari instance address.
+ * @param clusterName The name of a cluster associated with the Ambari instance.
+ *
+ * @return The removed data; A Map of configuration types and their corresponding versions.
+ */
+ Map<String, String> removeClusterConfigVersions(String address, String clusterName) {
+ Map<String, String> result = new HashMap<>();
+
+ configVersionsLock.writeLock().lock();
+ try {
+ if (ambariClusterConfigVersions.containsKey(address)) {
+ result.putAll(ambariClusterConfigVersions.get(address).remove(clusterName));
+ }
+ } finally {
+ configVersionsLock.writeLock().unlock();
+ }
+
+ // Delete the associated persisted record
+ File persisted = getConfigVersionsPersistenceFile(address, clusterName);
+ if (persisted.exists()) {
+ persisted.delete();
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the cluster configuration details for the specified cluster and Ambari instance.
+ *
+ * @param address An Ambari instance address.
+ * @param clusterName The name of a cluster associated with the Ambari instance.
+ *
+ * @return A Map of configuration types and their corresponding versions.
+ */
+ Map<String, String> getClusterConfigVersions(String address, String clusterName) {
+ Map<String, String> result = new HashMap<>();
+
+ configVersionsLock.readLock().lock();
+ try {
+ if (ambariClusterConfigVersions.containsKey(address)) {
+ result.putAll(ambariClusterConfigVersions.get(address).get(clusterName));
+ }
+ } finally {
+ configVersionsLock.readLock().unlock();
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Get all the clusters the monitor knows about.
+ *
+ * @return A Map of Ambari instance addresses to associated cluster names.
+ */
+ Map<String, List<String>> getClusterNames() {
+ Map<String, List<String>> result = new HashMap<>();
+
+ configVersionsLock.readLock().lock();
+ try {
+ for (String address : ambariClusterConfigVersions.keySet()) {
+ List<String> clusterNames = new ArrayList<>();
+ clusterNames.addAll(ambariClusterConfigVersions.get(address).keySet());
+ result.put(address, clusterNames);
+ }
+ } finally {
+ configVersionsLock.readLock().unlock();
+ }
+
+ return result;
+
+ }
+
+
+ /**
+ * Notify registered change listeners.
+ *
+ * @param source The address of the Ambari instance from which the cluster details were determined.
+ * @param clusterName The name of the cluster whose configuration details have changed.
+ */
+ void notifyChangeListeners(String source, String clusterName) {
+ for (ConfigurationChangeListener listener : changeListeners) {
+ listener.onConfigurationChange(source, clusterName);
+ }
+ }
+
+
+ /**
+ * Request the current active configuration version info from Ambari.
+ *
+ * @param address The Ambari instance address.
+ * @param clusterName The name of the cluster for which the details are desired.
+ *
+ * @return A Map of service configuration types and their corresponding versions.
+ */
+ Map<String, String> getUpdatedConfigVersions(String address, String clusterName) {
+ Map<String, String> configVersions = new HashMap<>();
+
- Map<String, Map<String, AmbariCluster.ServiceConfiguration>> serviceConfigs =
- ambariClient.getActiveServiceConfigurations(clusterName, getDiscoveryConfig(address, clusterName));
++ ServiceDiscoveryConfig sdc = getDiscoveryConfig(address, clusterName);
++ if (sdc != null) {
++ Map<String, Map<String, AmbariCluster.ServiceConfiguration>> serviceConfigs =
++ ambariClient.getActiveServiceConfigurations(clusterName, sdc);
+
- for (Map<String, AmbariCluster.ServiceConfiguration> serviceConfig : serviceConfigs.values()) {
- for (AmbariCluster.ServiceConfiguration config : serviceConfig.values()) {
- configVersions.put(config.getType(), config.getVersion());
++ for (Map<String, AmbariCluster.ServiceConfiguration> serviceConfig : serviceConfigs.values()) {
++ for (AmbariCluster.ServiceConfiguration config : serviceConfig.values()) {
++ configVersions.put(config.getType(), config.getVersion());
++ }
+ }
+ }
+
+ return configVersions;
+ }
+
+
+ /**
+ * The thread that polls Ambari for configuration details for clusters associated with discovered topologies,
+ * compares them with the current recorded values, and notifies any listeners when differences are discovered.
+ */
+ static final class PollingConfigAnalyzer implements Runnable {
+
+ private static final int DEFAULT_POLLING_INTERVAL = 60;
+
+ // Polling interval in seconds
+ private int interval = DEFAULT_POLLING_INTERVAL;
+
+ private AmbariConfigurationMonitor delegate;
+
+ private boolean isActive = false;
+
+ PollingConfigAnalyzer(AmbariConfigurationMonitor delegate) {
+ this.delegate = delegate;
+ this.interval = Integer.getInteger(INTERVAL_PROPERTY_NAME, PollingConfigAnalyzer.DEFAULT_POLLING_INTERVAL);
+ }
+
+ void setInterval(int interval) {
+ this.interval = interval;
+ }
+
+
+ void stop() {
+ isActive = false;
+ }
+
+ @Override
+ public void run() {
+ isActive = true;
+
+ log.startedAmbariConfigMonitor(interval);
+
+ while (isActive) {
+ for (Map.Entry<String, List<String>> entry : delegate.getClusterNames().entrySet()) {
+ String address = entry.getKey();
+ for (String clusterName : entry.getValue()) {
+ Map<String, String> configVersions = delegate.getClusterConfigVersions(address, clusterName);
+ if (configVersions != null && !configVersions.isEmpty()) {
+ Map<String, String> updatedVersions = delegate.getUpdatedConfigVersions(address, clusterName);
+ if (updatedVersions != null && !updatedVersions.isEmpty()) {
+ boolean configHasChanged = false;
+
+ // If the config sets don't match in size, then something has changed
+ if (updatedVersions.size() != configVersions.size()) {
+ configHasChanged = true;
+ } else {
+ // Perform the comparison of all the config versions
+ for (Map.Entry<String, String> configVersion : configVersions.entrySet()) {
+ if (!updatedVersions.get(configVersion.getKey()).equals(configVersion.getValue())) {
+ configHasChanged = true;
+ break;
+ }
+ }
+ }
+
+ // If a change has occurred, notify the listeners
+ if (configHasChanged) {
+ delegate.notifyChangeListeners(address, clusterName);
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ Thread.sleep(interval * 1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/e5fd0622/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariDynamicServiceURLCreator.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariDynamicServiceURLCreator.java
index 3c2269d,0000000..dc4ac49
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariDynamicServiceURLCreator.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariDynamicServiceURLCreator.java
@@@ -1,151 -1,0 +1,151 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.discovery.ambari;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
- class AmbariDynamicServiceURLCreator {
++class AmbariDynamicServiceURLCreator implements ServiceURLCreator {
+
+ static final String MAPPING_CONFIG_OVERRIDE_PROPERTY = "org.apache.gateway.topology.discovery.ambari.config";
+
+ private AmbariServiceDiscoveryMessages log = MessagesFactory.get(AmbariServiceDiscoveryMessages.class);
+
+ private AmbariCluster cluster = null;
+ private ServiceURLPropertyConfig config;
+
+ AmbariDynamicServiceURLCreator(AmbariCluster cluster) {
+ this.cluster = cluster;
+
+ String mappingConfiguration = System.getProperty(MAPPING_CONFIG_OVERRIDE_PROPERTY);
+ if (mappingConfiguration != null) {
+ File mappingConfigFile = new File(mappingConfiguration);
+ if (mappingConfigFile.exists()) {
+ try {
+ config = new ServiceURLPropertyConfig(mappingConfigFile);
+ log.loadedComponentConfigMappings(mappingConfigFile.getAbsolutePath());
+ } catch (Exception e) {
+ log.failedToLoadComponentConfigMappings(mappingConfigFile.getAbsolutePath(), e);
+ }
+ }
+ }
+
+ // If there is no valid override configured, fall-back to the internal mapping configuration
+ if (config == null) {
+ config = new ServiceURLPropertyConfig();
+ }
+ }
+
+ AmbariDynamicServiceURLCreator(AmbariCluster cluster, File mappingConfiguration) throws IOException {
+ this.cluster = cluster;
+ config = new ServiceURLPropertyConfig(new FileInputStream(mappingConfiguration));
+ }
+
+ AmbariDynamicServiceURLCreator(AmbariCluster cluster, String mappings) {
+ this.cluster = cluster;
+ config = new ServiceURLPropertyConfig(new ByteArrayInputStream(mappings.getBytes()));
+ }
+
- List<String> create(String serviceName) {
++ public List<String> create(String serviceName) {
+ List<String> urls = new ArrayList<>();
+
+ Map<String, String> placeholderValues = new HashMap<>();
+ List<String> componentHostnames = new ArrayList<>();
+ String hostNamePlaceholder = null;
+
+ ServiceURLPropertyConfig.URLPattern pattern = config.getURLPattern(serviceName);
+ if (pattern != null) {
+ for (String propertyName : pattern.getPlaceholders()) {
+ ServiceURLPropertyConfig.Property configProperty = config.getConfigProperty(serviceName, propertyName);
+
+ String propertyValue = null;
+ String propertyType = configProperty.getType();
+ if (ServiceURLPropertyConfig.Property.TYPE_SERVICE.equals(propertyType)) {
+ log.lookingUpServiceConfigProperty(configProperty.getService(), configProperty.getServiceConfig(), configProperty.getValue());
+ AmbariCluster.ServiceConfiguration svcConfig =
+ cluster.getServiceConfiguration(configProperty.getService(), configProperty.getServiceConfig());
+ if (svcConfig != null) {
+ propertyValue = svcConfig.getProperties().get(configProperty.getValue());
+ }
+ } else if (ServiceURLPropertyConfig.Property.TYPE_COMPONENT.equals(propertyType)) {
+ String compName = configProperty.getComponent();
+ if (compName != null) {
+ AmbariComponent component = cluster.getComponent(compName);
+ if (component != null) {
+ if (ServiceURLPropertyConfig.Property.PROP_COMP_HOSTNAME.equals(configProperty.getValue())) {
+ log.lookingUpComponentHosts(compName);
+ componentHostnames.addAll(component.getHostNames());
+ hostNamePlaceholder = propertyName; // Remember the host name placeholder
+ } else {
+ log.lookingUpComponentConfigProperty(compName, configProperty.getValue());
+ propertyValue = component.getConfigProperty(configProperty.getValue());
+ }
+ }
+ }
+ } else { // Derived property
+ log.handlingDerivedProperty(serviceName, configProperty.getType(), configProperty.getName());
+ ServiceURLPropertyConfig.Property p = config.getConfigProperty(serviceName, configProperty.getName());
+ propertyValue = p.getValue();
+ if (propertyValue == null) {
+ if (p.getConditionHandler() != null) {
+ propertyValue = p.getConditionHandler().evaluate(config, cluster);
+ }
+ }
+ }
+
+ log.determinedPropertyValue(configProperty.getName(), propertyValue);
+ placeholderValues.put(configProperty.getName(), propertyValue);
+ }
+
+ // For patterns with a placeholder value for the hostname (e.g., multiple URL scenarios)
+ if (!componentHostnames.isEmpty()) {
+ for (String componentHostname : componentHostnames) {
+ String url = pattern.get().replace("{" + hostNamePlaceholder + "}", componentHostname);
+ urls.add(createURL(url, placeholderValues));
+ }
+ } else { // Single URL result case
+ urls.add(createURL(pattern.get(), placeholderValues));
+ }
+ }
+
+ return urls;
+ }
+
+ private String createURL(String pattern, Map<String, String> placeholderValues) {
+ String url = null;
+ if (pattern != null) {
+ url = pattern;
+ for (String placeHolder : placeholderValues.keySet()) {
+ String value = placeholderValues.get(placeHolder);
+ if (value != null) {
+ url = url.replace("{" + placeHolder + "}", value);
+ }
+ }
+ }
+ return url;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/e5fd0622/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/PropertyEqualsHandler.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/PropertyEqualsHandler.java
index 4044d56,0000000..0dfab36
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/PropertyEqualsHandler.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/PropertyEqualsHandler.java
@@@ -1,76 -1,0 +1,88 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.discovery.ambari;
+
+
+class PropertyEqualsHandler implements ConditionalValueHandler {
+
+ private String serviceName = null;
+ private String propertyName = null;
+ private String propertyValue = null;
+ private ConditionalValueHandler affirmativeResult = null;
+ private ConditionalValueHandler negativeResult = null;
+
+ PropertyEqualsHandler(String serviceName,
+ String propertyName,
+ String propertyValue,
+ ConditionalValueHandler affirmativeResult,
+ ConditionalValueHandler negativeResult) {
+ this.serviceName = serviceName;
+ this.propertyName = propertyName;
+ this.propertyValue = propertyValue;
+ this.affirmativeResult = affirmativeResult;
+ this.negativeResult = negativeResult;
+ }
+
+ @Override
+ public String evaluate(ServiceURLPropertyConfig config, AmbariCluster cluster) {
+ String result = null;
+
+ ServiceURLPropertyConfig.Property p = config.getConfigProperty(serviceName, propertyName);
+ if (p != null) {
+ String value = getActualPropertyValue(cluster, p);
- if (propertyValue.equals(value)) {
- result = affirmativeResult.evaluate(config, cluster);
- } else if (negativeResult != null) {
- result = negativeResult.evaluate(config, cluster);
++ if (propertyValue == null) {
++ // If the property value isn't specified, then we're just checking if the property is set with any value
++ if (value != null) {
++ // So, if there is a value in the config, respond with the affirmative
++ result = affirmativeResult.evaluate(config, cluster);
++ } else if (negativeResult != null) {
++ result = negativeResult.evaluate(config, cluster);
++ }
++ }
++
++ if (result == null) {
++ if (propertyValue.equals(value)) {
++ result = affirmativeResult.evaluate(config, cluster);
++ } else if (negativeResult != null) {
++ result = negativeResult.evaluate(config, cluster);
++ }
+ }
+
+ // Check if the result is a reference to a local derived property
+ ServiceURLPropertyConfig.Property derived = config.getConfigProperty(serviceName, result);
+ if (derived != null) {
+ result = getActualPropertyValue(cluster, derived);
+ }
+ }
+
+ return result;
+ }
+
+ private String getActualPropertyValue(AmbariCluster cluster, ServiceURLPropertyConfig.Property property) {
+ String value = null;
+ String propertyType = property.getType();
+ if (ServiceURLPropertyConfig.Property.TYPE_COMPONENT.equals(propertyType)) {
+ AmbariComponent component = cluster.getComponent(property.getComponent());
+ if (component != null) {
+ value = component.getConfigProperty(property.getValue());
+ }
+ } else if (ServiceURLPropertyConfig.Property.TYPE_SERVICE.equals(propertyType)) {
+ value = cluster.getServiceConfiguration(property.getService(), property.getServiceConfig()).getProperties().get(property.getValue());
+ }
+ return value;
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/e5fd0622/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/ServiceURLPropertyConfig.java
----------------------------------------------------------------------
diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/ServiceURLPropertyConfig.java
index 47b20e9,0000000..9f3da3d
mode 100644,000000..100644
--- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/ServiceURLPropertyConfig.java
+++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/ServiceURLPropertyConfig.java
@@@ -1,324 -1,0 +1,329 @@@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway.topology.discovery.ambari;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.util.XmlUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Service URL pattern mapping configuration model.
+ */
+class ServiceURLPropertyConfig {
+
+ private static final AmbariServiceDiscoveryMessages log = MessagesFactory.get(AmbariServiceDiscoveryMessages.class);
+
+ private static final String ATTR_NAME = "name";
+
+ private static XPathExpression SERVICE_URL_PATTERN_MAPPINGS;
+ private static XPathExpression URL_PATTERN;
+ private static XPathExpression PROPERTIES;
+ static {
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ try {
+ SERVICE_URL_PATTERN_MAPPINGS = xpath.compile("/service-discovery-url-mappings/service");
+ URL_PATTERN = xpath.compile("url-pattern/text()");
+ PROPERTIES = xpath.compile("properties/property");
+ } catch (XPathExpressionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static final String DEFAULT_SERVICE_URL_MAPPINGS = "ambari-service-discovery-url-mappings.xml";
+
+ private Map<String, URLPattern> urlPatterns = new HashMap<>();
+
+ private Map<String, Map<String, Property>> properties = new HashMap<>();
+
+
+ /**
+ * The default service URL pattern to property mapping configuration will be used.
+ */
+ ServiceURLPropertyConfig() {
+ this(ServiceURLPropertyConfig.class.getClassLoader().getResourceAsStream(DEFAULT_SERVICE_URL_MAPPINGS));
+ }
+
+ /**
+ * The default service URL pattern to property mapping configuration will be used.
+ */
+ ServiceURLPropertyConfig(File mappingConfigurationFile) throws Exception {
+ this(new FileInputStream(mappingConfigurationFile));
+ }
+
+ /**
+ *
+ * @param source An InputStream for the XML content
+ */
+ ServiceURLPropertyConfig(InputStream source) {
+ // Parse the XML, and build the model
+ try {
+ Document doc = XmlUtils.readXml(source);
+
+ NodeList serviceNodes =
+ (NodeList) SERVICE_URL_PATTERN_MAPPINGS.evaluate(doc, XPathConstants.NODESET);
+ for (int i=0; i < serviceNodes.getLength(); i++) {
+ Node serviceNode = serviceNodes.item(i);
+ String serviceName = serviceNode.getAttributes().getNamedItem(ATTR_NAME).getNodeValue();
+ properties.put(serviceName, new HashMap<String, Property>());
+
+ Node urlPatternNode = (Node) URL_PATTERN.evaluate(serviceNode, XPathConstants.NODE);
+ if (urlPatternNode != null) {
+ urlPatterns.put(serviceName, new URLPattern(urlPatternNode.getNodeValue()));
+ }
+
+ NodeList propertiesNode = (NodeList) PROPERTIES.evaluate(serviceNode, XPathConstants.NODESET);
+ if (propertiesNode != null) {
+ processProperties(serviceName, propertiesNode);
+ }
+ }
+ } catch (Exception e) {
+ log.failedToLoadServiceDiscoveryURLDefConfiguration(e);
+ } finally {
+ try {
+ source.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private void processProperties(String serviceName, NodeList propertyNodes) {
+ for (int i = 0; i < propertyNodes.getLength(); i++) {
+ Property p = Property.createProperty(serviceName, propertyNodes.item(i));
+ properties.get(serviceName).put(p.getName(), p);
+ }
+ }
+
+ URLPattern getURLPattern(String service) {
+ return urlPatterns.get(service);
+ }
+
+ Property getConfigProperty(String service, String property) {
+ return properties.get(service).get(property);
+ }
+
+ static class URLPattern {
+ String pattern;
+ List<String> placeholders = new ArrayList<>();
+
+ URLPattern(String pattern) {
+ this.pattern = pattern;
+
+ final Pattern regex = Pattern.compile("\\{(.*?)}", Pattern.DOTALL);
+ final Matcher matcher = regex.matcher(pattern);
+ while( matcher.find() ){
+ placeholders.add(matcher.group(1));
+ }
+ }
+
+ String get() {return pattern; }
+ List<String> getPlaceholders() {
+ return placeholders;
+ }
+ }
+
+ static class Property {
+ static final String TYPE_SERVICE = "SERVICE";
+ static final String TYPE_COMPONENT = "COMPONENT";
+ static final String TYPE_DERIVED = "DERIVED";
+
+ static final String PROP_COMP_HOSTNAME = "component.host.name";
+
+ static final String ATTR_NAME = "name";
+ static final String ATTR_PROPERTY = "property";
+ static final String ATTR_VALUE = "value";
+
+ static XPathExpression HOSTNAME;
+ static XPathExpression SERVICE_CONFIG;
+ static XPathExpression COMPONENT;
+ static XPathExpression CONFIG_PROPERTY;
+ static XPathExpression IF;
+ static XPathExpression THEN;
+ static XPathExpression ELSE;
+ static XPathExpression TEXT;
+ static {
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ try {
+ HOSTNAME = xpath.compile("hostname");
+ SERVICE_CONFIG = xpath.compile("service-config");
+ COMPONENT = xpath.compile("component");
+ CONFIG_PROPERTY = xpath.compile("config-property");
+ IF = xpath.compile("if");
+ THEN = xpath.compile("then");
+ ELSE = xpath.compile("else");
+ TEXT = xpath.compile("text()");
+ } catch (XPathExpressionException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ String type;
+ String name;
+ String component;
+ String service;
+ String serviceConfig;
+ String value;
+ ConditionalValueHandler conditionHandler = null;
+
+ private Property(String type,
+ String propertyName,
+ String component,
+ String service,
+ String configType,
+ String value,
+ ConditionalValueHandler pch) {
+ this.type = type;
+ this.name = propertyName;
+ this.service = service;
+ this.component = component;
+ this.serviceConfig = configType;
+ this.value = value;
+ conditionHandler = pch;
+ }
+
+ static Property createProperty(String serviceName, Node propertyNode) {
+ String propertyName = propertyNode.getAttributes().getNamedItem(ATTR_NAME).getNodeValue();
+ String propertyType = null;
+ String serviceType = null;
+ String configType = null;
+ String componentType = null;
+ String value = null;
+ ConditionalValueHandler pch = null;
+
+ try {
+ Node hostNameNode = (Node) HOSTNAME.evaluate(propertyNode, XPathConstants.NODE);
+ if (hostNameNode != null) {
+ value = PROP_COMP_HOSTNAME;
+ }
+
+ // Check for a service-config node
+ Node scNode = (Node) SERVICE_CONFIG.evaluate(propertyNode, XPathConstants.NODE);
+ if (scNode != null) {
+ // Service config property
+ propertyType = Property.TYPE_SERVICE;
+ serviceType = scNode.getAttributes().getNamedItem(ATTR_NAME).getNodeValue();
+ Node scTextNode = (Node) TEXT.evaluate(scNode, XPathConstants.NODE);
+ configType = scTextNode.getNodeValue();
+ } else { // If not service-config node, check for a component config node
+ Node cNode = (Node) COMPONENT.evaluate(propertyNode, XPathConstants.NODE);
+ if (cNode != null) {
+ // Component config property
+ propertyType = Property.TYPE_COMPONENT;
+ componentType = cNode.getFirstChild().getNodeValue();
+ Node cTextNode = (Node) TEXT.evaluate(cNode, XPathConstants.NODE);
+ configType = cTextNode.getNodeValue();
+ componentType = cTextNode.getNodeValue();
+ }
+ }
+
+ // Check for a config property node
+ Node cpNode = (Node) CONFIG_PROPERTY.evaluate(propertyNode, XPathConstants.NODE);
+ if (cpNode != null) {
+ // Check for a condition element
+ Node ifNode = (Node) IF.evaluate(cpNode, XPathConstants.NODE);
+ if (ifNode != null) {
+ propertyType = TYPE_DERIVED;
+ pch = getConditionHandler(serviceName, ifNode);
+ } else {
+ Node cpTextNode = (Node) TEXT.evaluate(cpNode, XPathConstants.NODE);
+ value = cpTextNode.getNodeValue();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Create and return the property representation
+ return new Property(propertyType, propertyName, componentType, serviceType, configType, value, pch);
+ }
+
+ private static ConditionalValueHandler getConditionHandler(String serviceName, Node ifNode) throws Exception {
+ ConditionalValueHandler result = null;
+
+ if (ifNode != null) {
+ NamedNodeMap attrs = ifNode.getAttributes();
+ String comparisonPropName = attrs.getNamedItem(ATTR_PROPERTY).getNodeValue();
- String comparisonValue = attrs.getNamedItem(ATTR_VALUE).getNodeValue();
++
++ String comparisonValue = null;
++ Node valueNode = attrs.getNamedItem(ATTR_VALUE);
++ if (valueNode != null) {
++ comparisonValue = attrs.getNamedItem(ATTR_VALUE).getNodeValue();
++ }
+
+ ConditionalValueHandler affirmativeResult = null;
+ Node thenNode = (Node) THEN.evaluate(ifNode, XPathConstants.NODE);
+ if (thenNode != null) {
+ Node subIfNode = (Node) IF.evaluate(thenNode, XPathConstants.NODE);
+ if (subIfNode != null) {
+ affirmativeResult = getConditionHandler(serviceName, subIfNode);
+ } else {
+ affirmativeResult = new SimpleValueHandler(thenNode.getFirstChild().getNodeValue());
+ }
+ }
+
+ ConditionalValueHandler negativeResult = null;
+ Node elseNode = (Node) ELSE.evaluate(ifNode, XPathConstants.NODE);
+ if (elseNode != null) {
+ Node subIfNode = (Node) IF.evaluate(elseNode, XPathConstants.NODE);
+ if (subIfNode != null) {
+ negativeResult = getConditionHandler(serviceName, subIfNode);
+ } else {
+ negativeResult = new SimpleValueHandler(elseNode.getFirstChild().getNodeValue());
+ }
+ }
+
+ result = new PropertyEqualsHandler(serviceName,
+ comparisonPropName,
+ comparisonValue,
+ affirmativeResult,
+ negativeResult);
+ }
+
+ return result;
+ }
+
+ String getType() { return type; }
+ String getName() { return name; }
+ String getComponent() { return component; }
+ String getService() { return service; }
+ String getServiceConfig() { return serviceConfig; }
+ String getValue() {
+ return value;
+ }
+ ConditionalValueHandler getConditionHandler() { return conditionHandler; }
+ }
+}