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 2017/12/14 21:13:25 UTC
[38/49] knox git commit: Merge branch 'master' into
KNOX-998-Package_Restructuring
http://git-wip-us.apache.org/repos/asf/knox/blob/22a7304a/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
index 38653f4,0000000..c6e373d
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
@@@ -1,818 -1,0 +1,895 @@@
+/**
+ * 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.services.topology.impl;
+
+
+import org.apache.commons.digester3.Digester;
+import org.apache.commons.digester3.binder.DigesterLoader;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.monitor.FileAlterationListener;
+import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
+import org.apache.commons.io.monitor.FileAlterationMonitor;
+import org.apache.commons.io.monitor.FileAlterationObserver;
+import org.apache.knox.gateway.GatewayMessages;
++import org.apache.knox.gateway.GatewayServer;
+import org.apache.knox.gateway.audit.api.Action;
+import org.apache.knox.gateway.audit.api.ActionOutcome;
+import org.apache.knox.gateway.audit.api.AuditServiceFactory;
+import org.apache.knox.gateway.audit.api.Auditor;
+import org.apache.knox.gateway.audit.api.ResourceType;
+import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.service.definition.ServiceDefinition;
++import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
++import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.topology.TopologyService;
++import org.apache.knox.gateway.topology.ClusterConfigurationMonitorService;
+import org.apache.knox.gateway.topology.Topology;
+import org.apache.knox.gateway.topology.TopologyEvent;
+import org.apache.knox.gateway.topology.TopologyListener;
+import org.apache.knox.gateway.topology.TopologyMonitor;
+import org.apache.knox.gateway.topology.TopologyProvider;
+import org.apache.knox.gateway.topology.builder.TopologyBuilder;
++import org.apache.knox.gateway.topology.discovery.ClusterConfigurationMonitor;
++import org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitor;
++import org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitorFactory;
++import org.apache.knox.gateway.topology.simple.SimpleDescriptorHandler;
+import org.apache.knox.gateway.topology.validation.TopologyValidator;
+import org.apache.knox.gateway.topology.xml.AmbariFormatXmlTopologyRules;
+import org.apache.knox.gateway.topology.xml.KnoxFormatXmlTopologyRules;
+import org.apache.knox.gateway.util.ServiceDefinitionsLoader;
- import org.apache.knox.gateway.services.security.AliasService;
- import org.apache.knox.gateway.topology.simple.SimpleDescriptorHandler;
+import org.eclipse.persistence.jaxb.JAXBContextProperties;
+import org.xml.sax.SAXException;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.commons.digester3.binder.DigesterLoader.newLoader;
+
+
+public class DefaultTopologyService
+ extends FileAlterationListenerAdaptor
+ implements TopologyService, TopologyMonitor, TopologyProvider, FileFilter, FileAlterationListener {
+
+ private static Auditor auditor = AuditServiceFactory.getAuditService().getAuditor(
+ AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
+ AuditConstants.KNOX_COMPONENT_NAME);
+
+ private static final List<String> SUPPORTED_TOPOLOGY_FILE_EXTENSIONS = new ArrayList<String>();
+ static {
+ SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.add("xml");
+ SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.add("conf");
+ }
+
+ private static GatewayMessages log = MessagesFactory.get(GatewayMessages.class);
+ private static DigesterLoader digesterLoader = newLoader(new KnoxFormatXmlTopologyRules(), new AmbariFormatXmlTopologyRules());
+ private List<FileAlterationMonitor> monitors = new ArrayList<>();
+ private File topologiesDirectory;
+ private File sharedProvidersDirectory;
+ private File descriptorsDirectory;
+
+ private DescriptorsMonitor descriptorsMonitor;
+
+ private Set<TopologyListener> listeners;
+ private volatile Map<File, Topology> topologies;
+ private AliasService aliasService;
+
++ private RemoteConfigurationMonitor remoteMonitor = null;
+
+ private Topology loadTopology(File file) throws IOException, SAXException, URISyntaxException, InterruptedException {
+ final long TIMEOUT = 250; //ms
+ final long DELAY = 50; //ms
+ log.loadingTopologyFile(file.getAbsolutePath());
+ Topology topology;
+ long start = System.currentTimeMillis();
+ while (true) {
+ try {
+ topology = loadTopologyAttempt(file);
+ break;
+ } catch (IOException e) {
+ if (System.currentTimeMillis() - start < TIMEOUT) {
+ log.failedToLoadTopologyRetrying(file.getAbsolutePath(), Long.toString(DELAY), e);
+ Thread.sleep(DELAY);
+ } else {
+ throw e;
+ }
+ } catch (SAXException e) {
+ if (System.currentTimeMillis() - start < TIMEOUT) {
+ log.failedToLoadTopologyRetrying(file.getAbsolutePath(), Long.toString(DELAY), e);
+ Thread.sleep(DELAY);
+ } else {
+ throw e;
+ }
+ }
+ }
+ return topology;
+ }
+
+ private Topology loadTopologyAttempt(File file) throws IOException, SAXException, URISyntaxException {
+ Topology topology;
+ Digester digester = digesterLoader.newDigester();
+ TopologyBuilder topologyBuilder = digester.parse(FileUtils.openInputStream(file));
+ if (null == topologyBuilder) {
+ return null;
+ }
+ topology = topologyBuilder.build();
+ topology.setUri(file.toURI());
+ topology.setName(FilenameUtils.removeExtension(file.getName()));
+ topology.setTimestamp(file.lastModified());
+ return topology;
+ }
+
+ private void redeployTopology(Topology topology) {
+ File topologyFile = new File(topology.getUri());
+ try {
+ TopologyValidator tv = new TopologyValidator(topology);
+
+ if(tv.validateTopology()) {
+ throw new SAXException(tv.getErrorString());
+ }
+
+ long start = System.currentTimeMillis();
+ long limit = 1000L; // One second.
+ long elapsed = 1;
+ while (elapsed <= limit) {
+ try {
+ long origTimestamp = topologyFile.lastModified();
+ long setTimestamp = Math.max(System.currentTimeMillis(), topologyFile.lastModified() + elapsed);
+ if(topologyFile.setLastModified(setTimestamp)) {
+ long newTimstamp = topologyFile.lastModified();
+ if(newTimstamp > origTimestamp) {
+ break;
+ } else {
+ Thread.sleep(10);
+ elapsed = System.currentTimeMillis() - start;
+ continue;
+ }
+ } else {
+ auditor.audit(Action.REDEPLOY, topology.getName(), ResourceType.TOPOLOGY,
+ ActionOutcome.FAILURE);
+ log.failedToRedeployTopology(topology.getName());
+ break;
+ }
+ } catch (InterruptedException e) {
+ auditor.audit(Action.REDEPLOY, topology.getName(), ResourceType.TOPOLOGY,
+ ActionOutcome.FAILURE);
+ log.failedToRedeployTopology(topology.getName(), e);
+ e.printStackTrace();
+ }
+ }
+ } catch (SAXException e) {
+ auditor.audit(Action.REDEPLOY, topology.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE);
+ log.failedToRedeployTopology(topology.getName(), e);
+ }
+ }
+
+ private List<TopologyEvent> createChangeEvents(
+ Map<File, Topology> oldTopologies,
+ Map<File, Topology> newTopologies) {
+ ArrayList<TopologyEvent> events = new ArrayList<TopologyEvent>();
+ // Go through the old topologies and find anything that was deleted.
+ for (File file : oldTopologies.keySet()) {
+ if (!newTopologies.containsKey(file)) {
+ events.add(new TopologyEvent(TopologyEvent.Type.DELETED, oldTopologies.get(file)));
+ }
+ }
+ // Go through the new topologies and figure out what was updated vs added.
+ for (File file : newTopologies.keySet()) {
+ if (oldTopologies.containsKey(file)) {
+ Topology oldTopology = oldTopologies.get(file);
+ Topology newTopology = newTopologies.get(file);
+ if (newTopology.getTimestamp() > oldTopology.getTimestamp()) {
+ events.add(new TopologyEvent(TopologyEvent.Type.UPDATED, newTopologies.get(file)));
+ }
+ } else {
+ events.add(new TopologyEvent(TopologyEvent.Type.CREATED, newTopologies.get(file)));
+ }
+ }
+ return events;
+ }
+
++ private File calculateAbsoluteProvidersConfigDir(GatewayConfig config) {
++ File pcDir = new File(config.getGatewayProvidersConfigDir());
++ return pcDir.getAbsoluteFile();
++ }
++
++ private File calculateAbsoluteDescriptorsDir(GatewayConfig config) {
++ File descDir = new File(config.getGatewayDescriptorsDir());
++ return descDir.getAbsoluteFile();
++ }
++
+ private File calculateAbsoluteTopologiesDir(GatewayConfig config) {
+ File topoDir = new File(config.getGatewayTopologyDir());
+ topoDir = topoDir.getAbsoluteFile();
+ return topoDir;
+ }
+
+ private File calculateAbsoluteConfigDir(GatewayConfig config) {
- File configDir = null;
++ File configDir;
+
+ String path = config.getGatewayConfDir();
+ configDir = (path != null) ? new File(path) : (new File(config.getGatewayTopologyDir())).getParentFile();
+
+ return configDir.getAbsoluteFile();
+ }
+
+ private void initListener(FileAlterationMonitor monitor,
+ File directory,
+ FileFilter filter,
+ FileAlterationListener listener) {
+ monitors.add(monitor);
+ FileAlterationObserver observer = new FileAlterationObserver(directory, filter);
+ observer.addListener(listener);
+ monitor.addObserver(observer);
+ }
+
+ private void initListener(File directory, FileFilter filter, FileAlterationListener listener) throws IOException, SAXException {
+ // Increasing the monitoring interval to 5 seconds as profiling has shown
+ // this is rather expensive in terms of generated garbage objects.
+ initListener(new FileAlterationMonitor(5000L), directory, filter, listener);
+ }
+
+ private Map<File, Topology> loadTopologies(File directory) {
+ Map<File, Topology> map = new HashMap<>();
+ if (directory.isDirectory() && directory.canRead()) {
+ File[] existingTopologies = directory.listFiles(this);
+ if (existingTopologies != null) {
+ for (File file : existingTopologies) {
+ try {
+ Topology loadTopology = loadTopology(file);
+ if (null != loadTopology) {
+ map.put(file, loadTopology);
+ } else {
+ auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+ ActionOutcome.FAILURE);
+ log.failedToLoadTopology(file.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ // Maybe it makes sense to throw exception
+ auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+ ActionOutcome.FAILURE);
+ log.failedToLoadTopology(file.getAbsolutePath(), e);
+ } catch (SAXException e) {
+ // Maybe it makes sense to throw exception
+ auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+ ActionOutcome.FAILURE);
+ log.failedToLoadTopology(file.getAbsolutePath(), e);
+ } catch (Exception e) {
+ // Maybe it makes sense to throw exception
+ auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY,
+ ActionOutcome.FAILURE);
+ log.failedToLoadTopology(file.getAbsolutePath(), e);
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+ public void setAliasService(AliasService as) {
+ this.aliasService = as;
+ }
+
+ public void deployTopology(Topology t){
+
+ try {
+ File temp = new File(topologiesDirectory.getAbsolutePath() + "/" + t.getName() + ".xml.temp");
+ Package topologyPkg = Topology.class.getPackage();
+ String pkgName = topologyPkg.getName();
+ String bindingFile = pkgName.replace(".", "/") + "/topology_binding-xml.xml";
+
+ Map<String, Object> properties = new HashMap<>(1);
+ properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindingFile);
+ JAXBContext jc = JAXBContext.newInstance(pkgName, Topology.class.getClassLoader(), properties);
+ Marshaller mr = jc.createMarshaller();
+
+ mr.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ mr.marshal(t, temp);
+
+ File topology = new File(topologiesDirectory.getAbsolutePath() + "/" + t.getName() + ".xml");
+ if(!temp.renameTo(topology)) {
+ FileUtils.forceDelete(temp);
+ throw new IOException("Could not rename temp file");
+ }
+
+ // This code will check if the topology is valid, and retrieve the errors if it is not.
+ TopologyValidator validator = new TopologyValidator( topology.getAbsolutePath() );
+ if( !validator.validateTopology() ){
+ throw new SAXException( validator.getErrorString() );
+ }
+
+
+ } catch (JAXBException e) {
+ auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE);
+ log.failedToDeployTopology(t.getName(), e);
+ } catch (IOException io) {
+ auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE);
+ log.failedToDeployTopology(t.getName(), io);
+ } catch (SAXException sx){
+ auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE);
+ log.failedToDeployTopology(t.getName(), sx);
+ }
+ reloadTopologies();
+ }
+
+ public void redeployTopologies(String topologyName) {
+
+ for (Topology topology : getTopologies()) {
+ if (topologyName == null || topologyName.equals(topology.getName())) {
+ redeployTopology(topology);
+ }
+ }
+
+ }
+
+ public void reloadTopologies() {
+ try {
+ synchronized (this) {
+ Map<File, Topology> oldTopologies = topologies;
+ Map<File, Topology> newTopologies = loadTopologies(topologiesDirectory);
+ List<TopologyEvent> events = createChangeEvents(oldTopologies, newTopologies);
+ topologies = newTopologies;
+ notifyChangeListeners(events);
+ }
+ } catch (Exception e) {
+ // Maybe it makes sense to throw exception
+ log.failedToReloadTopologies(e);
+ }
+ }
+
+ public void deleteTopology(Topology t) {
+ File topoDir = topologiesDirectory;
+
+ if(topoDir.isDirectory() && topoDir.canRead()) {
+ for (File f : listFiles(topoDir)) {
+ String fName = FilenameUtils.getBaseName(f.getName());
+ if(fName.equals(t.getName())) {
+ f.delete();
+ }
+ }
+ }
+ reloadTopologies();
+ }
+
+ private void notifyChangeListeners(List<TopologyEvent> events) {
+ for (TopologyListener listener : listeners) {
+ try {
+ listener.handleTopologyEvent(events);
+ } catch (RuntimeException e) {
+ auditor.audit(Action.LOAD, "Topology_Event", ResourceType.TOPOLOGY, ActionOutcome.FAILURE);
+ log.failedToHandleTopologyEvents(e);
+ }
+ }
+ }
+
+ public Map<String, List<String>> getServiceTestURLs(Topology t, GatewayConfig config) {
+ File tFile = null;
+ Map<String, List<String>> urls = new HashMap<>();
+ if (topologiesDirectory.isDirectory() && topologiesDirectory.canRead()) {
+ for (File f : listFiles(topologiesDirectory)) {
+ if (FilenameUtils.removeExtension(f.getName()).equals(t.getName())) {
+ tFile = f;
+ }
+ }
+ }
+ Set<ServiceDefinition> defs;
+ if(tFile != null) {
+ defs = ServiceDefinitionsLoader.getServiceDefinitions(new File(config.getGatewayServicesDir()));
+
+ for(ServiceDefinition def : defs) {
+ urls.put(def.getRole(), def.getTestURLs());
+ }
+ }
+ return urls;
+ }
+
+ public Collection<Topology> getTopologies() {
+ Map<File, Topology> map = topologies;
+ return Collections.unmodifiableCollection(map.values());
+ }
+
+ @Override
+ public boolean deployProviderConfiguration(String name, String content) {
+ return writeConfig(sharedProvidersDirectory, name, content);
+ }
+
+ @Override
+ public Collection<File> getProviderConfigurations() {
+ List<File> providerConfigs = new ArrayList<>();
+ for (File providerConfig : listFiles(sharedProvidersDirectory)) {
+ if (SharedProviderConfigMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(providerConfig.getName()))) {
+ providerConfigs.add(providerConfig);
+ }
+ }
+ return providerConfigs;
+ }
+
+ @Override
+ public boolean deleteProviderConfiguration(String name) {
+ boolean result = false;
+
+ File providerConfig = getExistingFile(sharedProvidersDirectory, name);
+ if (providerConfig != null) {
+ List<String> references = descriptorsMonitor.getReferencingDescriptors(providerConfig.getAbsolutePath());
+ if (references.isEmpty()) {
+ result = providerConfig.delete();
+ } else {
+ log.preventedDeletionOfSharedProviderConfiguration(providerConfig.getAbsolutePath());
+ }
+ } else {
+ result = true; // If it already does NOT exist, then the delete effectively succeeded
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean deployDescriptor(String name, String content) {
+ return writeConfig(descriptorsDirectory, name, content);
+ }
+
+ @Override
+ public Collection<File> getDescriptors() {
+ List<File> descriptors = new ArrayList<>();
+ for (File descriptor : listFiles(descriptorsDirectory)) {
+ if (DescriptorsMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(descriptor.getName()))) {
+ descriptors.add(descriptor);
+ }
+ }
+ return descriptors;
+ }
+
+ @Override
+ public boolean deleteDescriptor(String name) {
+ File descriptor = getExistingFile(descriptorsDirectory, name);
+ return (descriptor == null) || descriptor.delete();
+ }
+
+ @Override
+ public void addTopologyChangeListener(TopologyListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void startMonitor() throws Exception {
++ // Start the local configuration monitors
+ for (FileAlterationMonitor monitor : monitors) {
+ monitor.start();
+ }
++
++ // Start the remote configuration monitor, if it has been initialized
++ if (remoteMonitor != null) {
++ try {
++ remoteMonitor.start();
++ } catch (Exception e) {
++ log.remoteConfigurationMonitorStartFailure(remoteMonitor.getClass().getTypeName(), e.getLocalizedMessage(), e);
++ }
++ }
+ }
+
+ @Override
+ public void stopMonitor() throws Exception {
++ // Stop the local configuration monitors
+ for (FileAlterationMonitor monitor : monitors) {
+ monitor.stop();
+ }
++
++ // Stop the remote configuration monitor, if it has been initialized
++ if (remoteMonitor != null) {
++ remoteMonitor.stop();
++ }
+ }
+
+ @Override
+ public boolean accept(File file) {
+ boolean accept = false;
+ if (!file.isDirectory() && file.canRead()) {
+ String extension = FilenameUtils.getExtension(file.getName());
+ if (SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.contains(extension)) {
+ accept = true;
+ }
+ }
+ return accept;
+ }
+
+ @Override
+ public void onFileCreate(File file) {
+ onFileChange(file);
+ }
+
+ @Override
+ public void onFileDelete(java.io.File file) {
+ // For full topology descriptors, we need to make sure to delete any corresponding simple descriptors to prevent
+ // unintended subsequent generation of the topology descriptor
+ for (String ext : DescriptorsMonitor.SUPPORTED_EXTENSIONS) {
+ File simpleDesc =
+ new File(descriptorsDirectory, FilenameUtils.getBaseName(file.getName()) + "." + ext);
+ if (simpleDesc.exists()) {
+ log.deletingDescriptorForTopologyDeletion(simpleDesc.getName(), file.getName());
+ simpleDesc.delete();
+ }
+ }
+
+ onFileChange(file);
+ }
+
+ @Override
+ public void onFileChange(File file) {
+ reloadTopologies();
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public void start() {
-
++ // Register a cluster configuration monitor listener for change notifications
++ ClusterConfigurationMonitorService ccms =
++ GatewayServer.getGatewayServices().getService(GatewayServices.CLUSTER_CONFIGURATION_MONITOR_SERVICE);
++ ccms.addListener(new TopologyDiscoveryTrigger(this));
+ }
+
+ @Override
+ public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
+
+ try {
- listeners = new HashSet<>();
++ listeners = new HashSet<>();
+ topologies = new HashMap<>();
+
+ topologiesDirectory = calculateAbsoluteTopologiesDir(config);
+
+ File configDirectory = calculateAbsoluteConfigDir(config);
+ descriptorsDirectory = new File(configDirectory, "descriptors");
+ sharedProvidersDirectory = new File(configDirectory, "shared-providers");
+
+ // Add support for conf/topologies
+ initListener(topologiesDirectory, this, this);
+
+ // Add support for conf/descriptors
+ descriptorsMonitor = new DescriptorsMonitor(topologiesDirectory, aliasService);
+ initListener(descriptorsDirectory,
+ descriptorsMonitor,
+ descriptorsMonitor);
+ log.monitoringDescriptorChangesInDirectory(descriptorsDirectory.getAbsolutePath());
+
+ // Add support for conf/shared-providers
+ SharedProviderConfigMonitor spm = new SharedProviderConfigMonitor(descriptorsMonitor, descriptorsDirectory);
+ initListener(sharedProvidersDirectory, spm, spm);
+ log.monitoringProviderConfigChangesInDirectory(sharedProvidersDirectory.getAbsolutePath());
+
+ // For all the descriptors currently in the descriptors dir at start-up time, trigger topology generation.
+ // This happens prior to the start-up loading of the topologies.
+ String[] descriptorFilenames = descriptorsDirectory.list();
+ if (descriptorFilenames != null) {
- for (String descriptorFilename : descriptorFilenames) {
- if (DescriptorsMonitor.isDescriptorFile(descriptorFilename)) {
- descriptorsMonitor.onFileChange(new File(descriptorsDirectory, descriptorFilename));
- }
++ for (String descriptorFilename : descriptorFilenames) {
++ if (DescriptorsMonitor.isDescriptorFile(descriptorFilename)) {
++ // If there isn't a corresponding topology file, or if the descriptor has been modified since the
++ // corresponding topology file was generated, then trigger generation of one
++ File matchingTopologyFile = getExistingFile(topologiesDirectory, FilenameUtils.getBaseName(descriptorFilename));
++ if (matchingTopologyFile == null ||
++ matchingTopologyFile.lastModified() < (new File(descriptorsDirectory, descriptorFilename)).lastModified()) {
++ descriptorsMonitor.onFileChange(new File(descriptorsDirectory, descriptorFilename));
++ }
+ }
++ }
+ }
+
++ // Initialize the remote configuration monitor, if it has been configured
++ remoteMonitor = RemoteConfigurationMonitorFactory.get(config);
++
+ } catch (IOException | SAXException io) {
+ throw new ServiceLifecycleException(io.getMessage());
+ }
+ }
+
-
+ /**
+ * Utility method for listing the files in the specified directory.
+ * This method is "nicer" than the File#listFiles() because it will not return null.
+ *
+ * @param directory The directory whose files should be returned.
+ *
+ * @return A List of the Files on the directory.
+ */
+ private static List<File> listFiles(File directory) {
- List<File> result = null;
++ List<File> result;
+ File[] files = directory.listFiles();
+ if (files != null) {
+ result = Arrays.asList(files);
+ } else {
+ result = Collections.emptyList();
+ }
+ return result;
+ }
+
+ /**
+ * Search for a file in the specified directory whose base name (filename without extension) matches the
+ * specified basename.
+ *
+ * @param directory The directory in which to search.
+ * @param basename The basename of interest.
+ *
+ * @return The matching File
+ */
+ private static File getExistingFile(File directory, String basename) {
+ File match = null;
+ for (File file : listFiles(directory)) {
+ if (FilenameUtils.getBaseName(file.getName()).equals(basename)) {
+ match = file;
+ break;
+ }
+ }
+ return match;
+ }
+
+ /**
+ * Write the specified content to a file.
+ *
+ * @param dest The destination directory.
+ * @param name The name of the file.
+ * @param content The contents of the file.
+ *
+ * @return true, if the write succeeds; otherwise, false.
+ */
+ private static boolean writeConfig(File dest, String name, String content) {
+ boolean result = false;
+
+ File destFile = new File(dest, name);
+ try {
+ FileUtils.writeStringToFile(destFile, content);
+ log.wroteConfigurationFile(destFile.getAbsolutePath());
+ result = true;
+ } catch (IOException e) {
+ log.failedToWriteConfigurationFile(destFile.getAbsolutePath(), e);
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Change handler for simple descriptors
+ */
+ public static class DescriptorsMonitor extends FileAlterationListenerAdaptor
+ implements FileFilter {
+
+ static final List<String> SUPPORTED_EXTENSIONS = new ArrayList<String>();
+ static {
+ SUPPORTED_EXTENSIONS.add("json");
+ SUPPORTED_EXTENSIONS.add("yml");
+ SUPPORTED_EXTENSIONS.add("yaml");
+ }
+
+ private File topologiesDir;
+
+ private AliasService aliasService;
+
+ private Map<String, List<String>> providerConfigReferences = new HashMap<>();
+
+
+ static boolean isDescriptorFile(String filename) {
+ return SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(filename));
+ }
+
+ public DescriptorsMonitor(File topologiesDir, AliasService aliasService) {
+ this.topologiesDir = topologiesDir;
+ this.aliasService = aliasService;
+ }
+
+ List<String> getReferencingDescriptors(String providerConfigPath) {
+ List<String> result = providerConfigReferences.get(FilenameUtils.normalize(providerConfigPath));
+ if (result == null) {
+ result = Collections.emptyList();
+ }
+ return result;
+ }
+
+ @Override
+ public void onFileCreate(File file) {
+ onFileChange(file);
+ }
+
+ @Override
+ public void onFileDelete(File file) {
+ // For simple descriptors, we need to make sure to delete any corresponding full topology descriptors to trigger undeployment
+ for (String ext : DefaultTopologyService.SUPPORTED_TOPOLOGY_FILE_EXTENSIONS) {
+ File topologyFile =
+ new File(topologiesDir, FilenameUtils.getBaseName(file.getName()) + "." + ext);
+ if (topologyFile.exists()) {
+ log.deletingTopologyForDescriptorDeletion(topologyFile.getName(), file.getName());
+ topologyFile.delete();
+ }
+ }
+
+ String normalizedFilePath = FilenameUtils.normalize(file.getAbsolutePath());
+ String reference = null;
+ for (Map.Entry<String, List<String>> entry : providerConfigReferences.entrySet()) {
+ if (entry.getValue().contains(normalizedFilePath)) {
+ reference = entry.getKey();
+ break;
+ }
+ }
+
+ if (reference != null) {
+ providerConfigReferences.get(reference).remove(normalizedFilePath);
+ log.removedProviderConfigurationReference(normalizedFilePath, reference);
+ }
+ }
+
+ @Override
+ public void onFileChange(File file) {
+ try {
+ // When a simple descriptor has been created or modified, generate the new topology descriptor
+ Map<String, File> result = SimpleDescriptorHandler.handle(file, topologiesDir, aliasService);
+ log.generatedTopologyForDescriptorChange(result.get("topology").getName(), file.getName());
+
+ // Add the provider config reference relationship for handling updates to the provider config
+ String providerConfig = FilenameUtils.normalize(result.get("reference").getAbsolutePath());
+ if (!providerConfigReferences.containsKey(providerConfig)) {
+ providerConfigReferences.put(providerConfig, new ArrayList<String>());
+ }
+ List<String> refs = providerConfigReferences.get(providerConfig);
+ String descriptorName = FilenameUtils.normalize(file.getAbsolutePath());
+ if (!refs.contains(descriptorName)) {
+ // Need to check if descriptor had previously referenced another provider config, so it can be removed
+ for (List<String> descs : providerConfigReferences.values()) {
+ if (descs.contains(descriptorName)) {
+ descs.remove(descriptorName);
+ }
+ }
+
+ // Add the current reference relationship
+ refs.add(descriptorName);
+ log.addedProviderConfigurationReference(descriptorName, providerConfig);
+ }
+ } catch (Exception e) {
+ log.simpleDescriptorHandlingError(file.getName(), e);
+ }
+ }
+
+ @Override
+ public boolean accept(File file) {
+ boolean accept = false;
+ if (!file.isDirectory() && file.canRead()) {
+ String extension = FilenameUtils.getExtension(file.getName());
+ if (SUPPORTED_EXTENSIONS.contains(extension)) {
+ accept = true;
+ }
+ }
+ return accept;
+ }
+ }
+
+ /**
+ * Change handler for shared provider configurations
+ */
+ public static class SharedProviderConfigMonitor extends FileAlterationListenerAdaptor
+ implements FileFilter {
+
+ static final List<String> SUPPORTED_EXTENSIONS = new ArrayList<>();
+ static {
+ SUPPORTED_EXTENSIONS.add("xml");
+ }
+
+ private DescriptorsMonitor descriptorsMonitor;
+ private File descriptorsDir;
+
+
+ SharedProviderConfigMonitor(DescriptorsMonitor descMonitor, File descriptorsDir) {
+ this.descriptorsMonitor = descMonitor;
+ this.descriptorsDir = descriptorsDir;
+ }
+
+ @Override
+ public void onFileCreate(File file) {
+ onFileChange(file);
+ }
+
+ @Override
+ public void onFileDelete(File file) {
+ onFileChange(file);
+ }
+
+ @Override
+ public void onFileChange(File file) {
+ // For shared provider configuration, we need to update any simple descriptors that reference it
+ for (File descriptor : getReferencingDescriptors(file)) {
+ descriptor.setLastModified(System.currentTimeMillis());
+ }
+ }
+
+ private List<File> getReferencingDescriptors(File sharedProviderConfig) {
+ List<File> references = new ArrayList<>();
+
+ for (File descriptor : listFiles(descriptorsDir)) {
+ if (DescriptorsMonitor.SUPPORTED_EXTENSIONS.contains(FilenameUtils.getExtension(descriptor.getName()))) {
+ for (String reference : descriptorsMonitor.getReferencingDescriptors(FilenameUtils.normalize(sharedProviderConfig.getAbsolutePath()))) {
+ references.add(new File(reference));
+ }
+ }
+ }
+
+ return references;
+ }
+
+ @Override
+ public boolean accept(File file) {
+ boolean accept = false;
+ if (!file.isDirectory() && file.canRead()) {
+ String extension = FilenameUtils.getExtension(file.getName());
+ if (SUPPORTED_EXTENSIONS.contains(extension)) {
+ accept = true;
+ }
+ }
+ return accept;
+ }
+ }
+
++ /**
++ * Listener for Ambari config change events, which will trigger re-generation (including re-discovery) of the
++ * affected topologies.
++ */
++ private static class TopologyDiscoveryTrigger implements ClusterConfigurationMonitor.ConfigurationChangeListener {
++
++ private TopologyService topologyService = null;
++
++ TopologyDiscoveryTrigger(TopologyService topologyService) {
++ this.topologyService = topologyService;
++ }
++
++ @Override
++ public void onConfigurationChange(String source, String clusterName) {
++ log.noticedClusterConfigurationChange(source, clusterName);
++ try {
++ // Identify any descriptors associated with the cluster configuration change
++ for (File descriptor : topologyService.getDescriptors()) {
++ String descriptorContent = FileUtils.readFileToString(descriptor);
++ if (descriptorContent.contains(source)) {
++ if (descriptorContent.contains(clusterName)) {
++ log.triggeringTopologyRegeneration(source, clusterName, descriptor.getAbsolutePath());
++ // 'Touch' the descriptor to trigger re-generation of the associated topology
++ descriptor.setLastModified(System.currentTimeMillis());
++ }
++ }
++ }
++ } catch (Exception e) {
++ log.errorRespondingToConfigChange(source, clusterName, e);
++ }
++ }
++ }
++
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/22a7304a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactory.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactory.java
index 254dca1,0000000..4def2b7
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactory.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactory.java
@@@ -1,71 -1,0 +1,71 @@@
+/**
+ * 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.simple;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import org.apache.commons.io.FilenameUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+
+public class SimpleDescriptorFactory {
+
+ /**
+ * Create a SimpleDescriptor from the specified file.
+ *
+ * @param path The path to the file.
+ * @return A SimpleDescriptor based on the contents of the file.
+ *
+ * @throws IOException
+ */
+ public static SimpleDescriptor parse(String path) throws IOException {
+ SimpleDescriptor sd;
+
+ if (path.endsWith(".json")) {
+ sd = parseJSON(path);
- } else if (path.endsWith(".yml")) {
++ } else if (path.endsWith(".yml") || path.endsWith(".yaml")) {
+ sd = parseYAML(path);
+ } else {
+ throw new IllegalArgumentException("Unsupported simple descriptor format: " + path.substring(path.lastIndexOf('.')));
+ }
+
+ return sd;
+ }
+
+
+ static SimpleDescriptor parseJSON(String path) throws IOException {
+ final ObjectMapper mapper = new ObjectMapper();
+ SimpleDescriptorImpl sd = mapper.readValue(new File(path), SimpleDescriptorImpl.class);
+ if (sd != null) {
+ sd.setName(FilenameUtils.getBaseName(path));
+ }
+ return sd;
+ }
+
+
+ static SimpleDescriptor parseYAML(String path) throws IOException {
+ final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ SimpleDescriptorImpl sd = mapper.readValue(new File(path), SimpleDescriptorImpl.class);
+ if (sd != null) {
+ sd.setName(FilenameUtils.getBaseName(path));
+ }
+ return sd;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/22a7304a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
index 2e3214d,0000000..30786dc
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
@@@ -1,316 -1,0 +1,382 @@@
+/**
+ * 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.simple;
+
- import org.apache.knox.gateway.i18n.messages.MessagesFactory;
- import org.apache.knox.gateway.services.Service;
- import org.apache.knox.gateway.topology.discovery.DefaultServiceDiscoveryConfig;
- import org.apache.knox.gateway.topology.discovery.ServiceDiscovery;
- import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryFactory;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
++import org.apache.knox.gateway.GatewayServer;
++import org.apache.knox.gateway.i18n.messages.MessagesFactory;
++import org.apache.knox.gateway.services.GatewayServices;
++import org.apache.knox.gateway.services.Service;
++import org.apache.knox.gateway.services.security.AliasService;
++import org.apache.knox.gateway.services.security.KeystoreService;
++import org.apache.knox.gateway.services.security.MasterService;
++import org.apache.knox.gateway.topology.discovery.DefaultServiceDiscoveryConfig;
++import org.apache.knox.gateway.topology.discovery.ServiceDiscovery;
++import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryFactory;
+
+
+/**
+ * Processes simple topology descriptors, producing full topology files, which can subsequently be deployed to the
+ * gateway.
+ */
+public class SimpleDescriptorHandler {
+
+ private static final Service[] NO_GATEWAY_SERVICES = new Service[]{};
+
+ private static final SimpleDescriptorMessages log = MessagesFactory.get(SimpleDescriptorMessages.class);
+
++ private static Map<String, ServiceDiscovery> discoveryInstances = new HashMap<>();
++
+ public static Map<String, File> handle(File desc) throws IOException {
+ return handle(desc, NO_GATEWAY_SERVICES);
+ }
+
+ public static Map<String, File> handle(File desc, Service...gatewayServices) throws IOException {
+ return handle(desc, desc.getParentFile(), gatewayServices);
+ }
+
+ public static Map<String, File> handle(File desc, File destDirectory) throws IOException {
+ return handle(desc, destDirectory, NO_GATEWAY_SERVICES);
+ }
+
+ public static Map<String, File> handle(File desc, File destDirectory, Service...gatewayServices) throws IOException {
+ return handle(SimpleDescriptorFactory.parse(desc.getAbsolutePath()), desc.getParentFile(), destDirectory, gatewayServices);
+ }
+
+ public static Map<String, File> handle(SimpleDescriptor desc, File srcDirectory, File destDirectory) {
+ return handle(desc, srcDirectory, destDirectory, NO_GATEWAY_SERVICES);
+ }
+
+ public static Map<String, File> handle(SimpleDescriptor desc, File srcDirectory, File destDirectory, Service...gatewayServices) {
+ Map<String, File> result = new HashMap<>();
+
+ File topologyDescriptor;
+
+ DefaultServiceDiscoveryConfig sdc = new DefaultServiceDiscoveryConfig(desc.getDiscoveryAddress());
+ sdc.setUser(desc.getDiscoveryUser());
+ sdc.setPasswordAlias(desc.getDiscoveryPasswordAlias());
+
+ // Use the discovery type from the descriptor. If it's unspecified, employ the default type.
+ String discoveryType = desc.getDiscoveryType();
+ if (discoveryType == null) {
+ discoveryType = "AMBARI";
+ }
+
- ServiceDiscovery sd = ServiceDiscoveryFactory.get(discoveryType, gatewayServices);
++ // Use the cached discovery object for the required type, if it has already been loaded
++ ServiceDiscovery sd = discoveryInstances.get(discoveryType);
++ if (sd == null) {
++ sd = ServiceDiscoveryFactory.get(discoveryType, gatewayServices);
++ discoveryInstances.put(discoveryType, sd);
++ }
+ ServiceDiscovery.Cluster cluster = sd.discover(sdc, desc.getClusterName());
+
+ List<String> validServiceNames = new ArrayList<>();
+
+ Map<String, Map<String, String>> serviceParams = new HashMap<>();
+ Map<String, List<String>> serviceURLs = new HashMap<>();
+
+ if (cluster != null) {
+ for (SimpleDescriptor.Service descService : desc.getServices()) {
+ String serviceName = descService.getName();
+
+ List<String> descServiceURLs = descService.getURLs();
+ if (descServiceURLs == null || descServiceURLs.isEmpty()) {
+ descServiceURLs = cluster.getServiceURLs(serviceName);
+ }
+
+ // Validate the discovered service URLs
+ List<String> validURLs = new ArrayList<>();
+ if (descServiceURLs != null && !descServiceURLs.isEmpty()) {
+ // Validate the URL(s)
+ for (String descServiceURL : descServiceURLs) {
+ if (validateURL(serviceName, descServiceURL)) {
+ validURLs.add(descServiceURL);
+ }
+ }
+
+ if (!validURLs.isEmpty()) {
+ validServiceNames.add(serviceName);
+ }
+ }
+
+ // If there is at least one valid URL associated with the service, then add it to the map
+ if (!validURLs.isEmpty()) {
+ serviceURLs.put(serviceName, validURLs);
+ } else {
+ log.failedToDiscoverClusterServiceURLs(serviceName, cluster.getName());
+ }
+
+ // Service params
+ if (descService.getParams() != null) {
+ serviceParams.put(serviceName, descService.getParams());
+ if (!validServiceNames.contains(serviceName)) {
+ validServiceNames.add(serviceName);
+ }
+ }
+ }
+ } else {
+ log.failedToDiscoverClusterServices(desc.getClusterName());
+ }
+
++ // Provision the query param encryption password here, rather than relying on the random password generated
++ // when the topology is deployed. This is to support Knox HA deployments, where multiple Knox instances are
++ // generating topologies based on a shared remote descriptor, and they must all be able to encrypt/decrypt
++ // query params with the same credentials. (KNOX-1136)
++ if (!provisionQueryParamEncryptionCredential(desc.getName())) {
++ log.unableCreatePasswordForEncryption(desc.getName());
++ }
++
+ BufferedWriter fw = null;
+ topologyDescriptor = null;
+ File providerConfig;
+ try {
+ // Verify that the referenced provider configuration exists before attempting to reading it
+ providerConfig = resolveProviderConfigurationReference(desc.getProviderConfig(), srcDirectory);
+ if (providerConfig == null) {
+ log.failedToResolveProviderConfigRef(desc.getProviderConfig());
+ throw new IllegalArgumentException("Unresolved provider configuration reference: " +
+ desc.getProviderConfig() + " ; Topology update aborted!");
+ }
+ result.put("reference", providerConfig);
+
+ // TODO: Should the contents of the provider config be validated before incorporating it into the topology?
+
+ String topologyFilename = desc.getName();
+ if (topologyFilename == null) {
+ topologyFilename = desc.getClusterName();
+ }
+ topologyDescriptor = new File(destDirectory, topologyFilename + ".xml");
+
+ fw = new BufferedWriter(new FileWriter(topologyDescriptor));
+
+ fw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+
+ fw.write("<!--==============================================-->\n");
+ fw.write("<!-- DO NOT EDIT. This is an auto-generated file. -->\n");
+ fw.write("<!--==============================================-->\n");
+
+ fw.write("<topology>\n");
+
+ // KNOX-1105 Indicate that this topology was auto-generated
+ fw.write(" <generated>true</generated>\n");
+
+ // Copy the externalized provider configuration content into the topology descriptor in-line
+ InputStreamReader policyReader = new InputStreamReader(new FileInputStream(providerConfig));
+ char[] buffer = new char[1024];
+ int count;
+ while ((count = policyReader.read(buffer)) > 0) {
+ fw.write(buffer, 0, count);
+ }
+ policyReader.close();
+
+ // Services
+ // Sort the service names to write the services alphabetically
+ List<String> serviceNames = new ArrayList<>(validServiceNames);
+ Collections.sort(serviceNames);
+
+ // Write the service declarations
+ for (String serviceName : serviceNames) {
++ fw.write("\n");
+ fw.write(" <service>\n");
+ fw.write(" <role>" + serviceName + "</role>\n");
+
+ // URLs
+ List<String> urls = serviceURLs.get(serviceName);
+ if (urls != null) {
+ for (String url : urls) {
+ fw.write(" <url>" + url + "</url>\n");
+ }
+ }
+
+ // Params
+ Map<String, String> svcParams = serviceParams.get(serviceName);
+ if (svcParams != null) {
+ for (String paramName : svcParams.keySet()) {
+ fw.write(" <param>\n");
+ fw.write(" <name>" + paramName + "</name>\n");
+ fw.write(" <value>" + svcParams.get(paramName) + "</value>\n");
+ fw.write(" </param>\n");
+ }
+ }
+
+ fw.write(" </service>\n");
+ }
+
+ // Applications
+ List<SimpleDescriptor.Application> apps = desc.getApplications();
+ if (apps != null) {
+ for (SimpleDescriptor.Application app : apps) {
+ fw.write(" <application>\n");
+ fw.write(" <name>" + app.getName() + "</name>\n");
+
+ // URLs
+ List<String> urls = app.getURLs();
+ if (urls != null) {
+ for (String url : urls) {
+ fw.write(" <url>" + url + "</url>\n");
+ }
+ }
+
+ // Params
+ Map<String, String> appParams = app.getParams();
+ if (appParams != null) {
+ for (String paramName : appParams.keySet()) {
+ fw.write(" <param>\n");
+ fw.write(" <name>" + paramName + "</name>\n");
+ fw.write(" <value>" + appParams.get(paramName) + "</value>\n");
+ fw.write(" </param>\n");
+ }
+ }
+
+ fw.write(" </application>\n");
+ }
+ }
+
+ fw.write("</topology>\n");
+
+ fw.flush();
+ } catch (IOException e) {
+ log.failedToGenerateTopologyFromSimpleDescriptor(topologyDescriptor.getName(), e);
+ topologyDescriptor.delete();
+ } finally {
+ if (fw != null) {
+ try {
+ fw.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ result.put("topology", topologyDescriptor);
+ return result;
+ }
+
+
++ /**
++ * KNOX-1136
++ *
++ * Provision the query string encryption password prior to it being randomly generated during the topology
++ * deployment.
++ *
++ * @param topologyName The name of the topology for which the credential will be provisioned.
++ *
++ * @return true if the credential was successfully provisioned; otherwise, false.
++ */
++ private static boolean provisionQueryParamEncryptionCredential(String topologyName) {
++ boolean result = false;
++
++ try {
++ GatewayServices services = GatewayServer.getGatewayServices();
++ if (services != null) {
++ MasterService ms = services.getService("MasterService");
++ if (ms != null) {
++ KeystoreService ks = services.getService(GatewayServices.KEYSTORE_SERVICE);
++ if (ks != null) {
++ if (!ks.isCredentialStoreForClusterAvailable(topologyName)) {
++ ks.createCredentialStoreForCluster(topologyName);
++ }
++
++ // If the credential store existed, or it was just successfully created
++ if (ks.getCredentialStoreForCluster(topologyName) != null) {
++ AliasService aliasService = services.getService(GatewayServices.ALIAS_SERVICE);
++ if (aliasService != null) {
++ // Derive and set the query param encryption password
++ String queryEncryptionPass = new String(ms.getMasterSecret()) + topologyName;
++ aliasService.addAliasForCluster(topologyName, "encryptQueryString", queryEncryptionPass);
++ result = true;
++ }
++ }
++ }
++ }
++ }
++ } catch (Exception e) {
++ log.exceptionCreatingPasswordForEncryption(topologyName, e);
++ }
++
++ return result;
++ }
++
++
+ private static boolean validateURL(String serviceName, String url) {
+ boolean result = false;
+
+ if (url != null && !url.isEmpty()) {
+ try {
+ new URI(url);
+ result = true;
+ } catch (URISyntaxException e) {
+ log.serviceURLValidationFailed(serviceName, url, e);
+ }
+ }
+
+ return result;
+ }
+
+
+ private static File resolveProviderConfigurationReference(String reference, File srcDirectory) {
+ File providerConfig;
+
+ // If the reference includes a path
+ if (reference.contains(File.separator)) {
+ // Check if it's an absolute path
+ providerConfig = new File(reference);
+ if (!providerConfig.exists()) {
+ // If it's not an absolute path, try treating it as a relative path
+ providerConfig = new File(srcDirectory, reference);
+ if (!providerConfig.exists()) {
+ providerConfig = null;
+ }
+ }
+ } else { // No file path, just a name
+ // Check if it's co-located with the referencing descriptor
+ providerConfig = new File(srcDirectory, reference);
+ if (!providerConfig.exists()) {
+ // Check the shared-providers config location
+ File sharedProvidersDir = new File(srcDirectory, "../shared-providers");
+ if (sharedProvidersDir.exists()) {
+ providerConfig = new File(sharedProvidersDir, reference);
+ if (!providerConfig.exists()) {
+ // Check if it's a valid name without the extension
+ providerConfig = new File(sharedProvidersDir, reference + ".xml");
+ if (!providerConfig.exists()) {
+ providerConfig = null;
+ }
+ }
+ }
+ }
+ }
+
+ return providerConfig;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/22a7304a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorMessages.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorMessages.java
index 07c4350,0000000..28962f9
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorMessages.java
@@@ -1,50 -1,0 +1,59 @@@
+/**
+ * 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.simple;
+
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+
+@Messages(logger="org.apache.gateway.topology.simple")
+public interface SimpleDescriptorMessages {
+
+ @Message(level = MessageLevel.ERROR,
+ text = "Service discovery for cluster {0} failed.")
+ void failedToDiscoverClusterServices(final String cluster);
+
+ @Message(level = MessageLevel.ERROR,
+ text = "No valid URLs were discovered for {0} in the {1} cluster.")
+ void failedToDiscoverClusterServiceURLs(final String serviceName, final String clusterName);
+
+ @Message(level = MessageLevel.ERROR,
+ text = "Failed to resolve the referenced provider configuration {0}.")
+ void failedToResolveProviderConfigRef(final String providerConfigRef);
+
+ @Message(level = MessageLevel.ERROR,
+ text = "URL validation failed for {0} URL {1} : {2}")
+ void serviceURLValidationFailed(final String serviceName,
+ final String url,
+ @StackTrace( level = MessageLevel.DEBUG ) Exception e );
+
+ @Message(level = MessageLevel.ERROR,
+ text = "Error generating topology {0} from simple descriptor: {1}")
+ void failedToGenerateTopologyFromSimpleDescriptor(final String topologyFile,
+ @StackTrace( level = MessageLevel.DEBUG ) Exception e );
+
++ @Message( level = MessageLevel.ERROR,
++ text = "Error creating a password for query string encryption for {0}: {1}" )
++ void exceptionCreatingPasswordForEncryption(String topologyName,
++ @StackTrace( level = MessageLevel.DEBUG) Exception e);
++
++ @Message( level = MessageLevel.ERROR,
++ text = "Failed to create a password for query string encryption for {0}." )
++ void unableCreatePasswordForEncryption(String topologyName);
++
+}