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/master Commit: e5fd0622493a7e3c62811ea03ff7931c979dd87a Parents: e766b3b 99e6a54 Author: Sandeep More <[email protected]> Authored: Tue Jan 9 14:25:16 2018 -0500 Committer: Sandeep More <[email protected]> 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; } + } +}
