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/11/02 18:48:22 UTC
[18/25] knox git commit: Merge branch 'master' into
KNOX-998-Package_Restructuring
http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/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 455b0fa,0000000..38653f4
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,689 -1,0 +1,818 @@@
+/**
+ * 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.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.ServiceLifecycleException;
+import org.apache.knox.gateway.services.topology.TopologyService;
+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.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 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 calculateAbsoluteTopologiesDir(GatewayConfig config) {
- String normalizedTopologyDir = FilenameUtils.normalize(config.getGatewayTopologyDir());
- File topoDir = new File(normalizedTopologyDir);
++ File topoDir = new File(config.getGatewayTopologyDir());
+ topoDir = topoDir.getAbsoluteFile();
+ return topoDir;
+ }
+
+ private File calculateAbsoluteConfigDir(GatewayConfig config) {
+ File configDir = null;
+
- String path = FilenameUtils.normalize(config.getGatewayConfDir());
- if (path != null) {
- configDir = new File(config.getGatewayConfDir());
- } else {
- configDir = (new File(config.getGatewayTopologyDir())).getParentFile();
- }
- configDir = configDir.getAbsoluteFile();
++ String path = config.getGatewayConfDir();
++ configDir = (path != null) ? new File(path) : (new File(config.getGatewayTopologyDir())).getParentFile();
+
- return configDir;
++ 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()) {
- for (File file : directory.listFiles(this)) {
- try {
- Topology loadTopology = loadTopology(file);
- if (null != loadTopology) {
- map.put(file, loadTopology);
- } else {
++ 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());
++ 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);
+ }
- } 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()) {
- File[] results = topoDir.listFiles();
- for (File f : results) {
++ 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 : topologiesDirectory.listFiles()){
- if(FilenameUtils.removeExtension(f.getName()).equals(t.getName())){
++ 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 {
+ for (FileAlterationMonitor monitor : monitors) {
+ monitor.start();
+ }
+ }
+
+ @Override
+ public void stopMonitor() throws Exception {
+ for (FileAlterationMonitor monitor : monitors) {
+ monitor.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() {
+
+ }
+
+ @Override
+ public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
+
+ try {
+ listeners = new HashSet<>();
+ topologies = new HashMap<>();
+
+ topologiesDirectory = calculateAbsoluteTopologiesDir(config);
+
+ File configDirectory = calculateAbsoluteConfigDir(config);
+ descriptorsDirectory = new File(configDirectory, "descriptors");
- File sharedProvidersDirectory = new File(configDirectory, "shared-providers");
++ sharedProvidersDirectory = new File(configDirectory, "shared-providers");
+
+ // Add support for conf/topologies
+ initListener(topologiesDirectory, this, this);
+
+ // Add support for conf/descriptors
- DescriptorsMonitor dm = new DescriptorsMonitor(topologiesDirectory, aliasService);
++ descriptorsMonitor = new DescriptorsMonitor(topologiesDirectory, aliasService);
+ initListener(descriptorsDirectory,
- dm,
- dm);
++ descriptorsMonitor,
++ descriptorsMonitor);
++ log.monitoringDescriptorChangesInDirectory(descriptorsDirectory.getAbsolutePath());
+
+ // Add support for conf/shared-providers
- SharedProviderConfigMonitor spm = new SharedProviderConfigMonitor(dm, descriptorsDirectory);
++ 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)) {
- dm.onFileChange(new File(descriptorsDirectory, descriptorFilename));
++ descriptorsMonitor.onFileChange(new File(descriptorsDirectory, descriptorFilename));
+ }
+ }
+ }
+
+ } 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;
++ 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(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 : descriptorsDir.listFiles()) {
++ 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;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
index 1caa946,0000000..a1a2609
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
@@@ -1,94 -1,0 +1,105 @@@
+/**
+ * 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.builder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.knox.gateway.topology.Application;
+import org.apache.knox.gateway.topology.Provider;
+import org.apache.knox.gateway.topology.Service;
+import org.apache.knox.gateway.topology.Topology;
+
+public class BeanPropertyTopologyBuilder implements TopologyBuilder {
+
+ private String name;
++ private String defaultService;
+ private List<Provider> providers;
+ private List<Service> services;
+ private List<Application> applications;
+
+ public BeanPropertyTopologyBuilder() {
+ providers = new ArrayList<Provider>();
+ services = new ArrayList<Service>();
+ applications = new ArrayList<Application>();
+ }
+
+ public BeanPropertyTopologyBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String name() {
+ return name;
+ }
+
++ public BeanPropertyTopologyBuilder defaultService(String defaultService) {
++ this.defaultService = defaultService;
++ return this;
++ }
++
++ public String defaultService() {
++ return defaultService;
++ }
++
+ public BeanPropertyTopologyBuilder addProvider(Provider provider) {
+ providers.add(provider);
+ return this;
+ }
+
+ public List<Provider> providers() {
+ return providers;
+ }
+
+ public BeanPropertyTopologyBuilder addService(Service service) {
+ services.add(service);
+ return this;
+ }
+
+ public List<Service> services() {
+ return services;
+ }
+
+ public BeanPropertyTopologyBuilder addApplication( Application application ) {
+ applications.add(application);
+ return this;
+ }
+
+ public List<Application> applications() {
+ return applications;
+ }
+
+ public Topology build() {
+ Topology topology = new Topology();
+ topology.setName(name);
++ topology.setDefaultServicePath(defaultService);
+
+ for (Provider provider : providers) {
+ topology.addProvider(provider);
+ }
+
+ for (Service service : services) {
+ topology.addService(service);
+ }
+
+ for (Application application : applications) {
+ topology.addApplication(application);
+ }
+
+ return topology;
+ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
----------------------------------------------------------------------
diff --cc gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
index 6b51ab8,0000000..81aedec
mode 100644,000000..100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
@@@ -1,93 -1,0 +1,95 @@@
+/**
+ * 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.xml;
+
+import org.apache.commons.digester3.Rule;
+import org.apache.commons.digester3.binder.AbstractRulesModule;
+import org.apache.knox.gateway.topology.Application;
+import org.apache.knox.gateway.topology.Param;
+import org.apache.knox.gateway.topology.Provider;
+import org.apache.knox.gateway.topology.Service;
+import org.apache.knox.gateway.topology.Version;
+import org.apache.knox.gateway.topology.builder.BeanPropertyTopologyBuilder;
+import org.xml.sax.Attributes;
+
+public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
+
+ private static final String ROOT_TAG = "topology";
+ private static final String NAME_TAG = "name";
+ private static final String VERSION_TAG = "version";
++ private static final String DEFAULT_SERVICE_TAG = "path";
+ private static final String APPLICATION_TAG = "application";
+ private static final String SERVICE_TAG = "service";
+ private static final String ROLE_TAG = "role";
+ private static final String URL_TAG = "url";
+ private static final String PROVIDER_TAG = "gateway/provider";
+ private static final String ENABLED_TAG = "enabled";
+ private static final String PARAM_TAG = "param";
+ private static final String VALUE_TAG = "value";
+
+ private static final Rule paramRule = new ParamRule();
+
+ @Override
+ protected void configure() {
+ forPattern( ROOT_TAG ).createObject().ofType( BeanPropertyTopologyBuilder.class );
+ forPattern( ROOT_TAG + "/" + NAME_TAG ).callMethod("name").usingElementBodyAsArgument();
+ forPattern( ROOT_TAG + "/" + VERSION_TAG ).callMethod("version").usingElementBodyAsArgument();
++ forPattern( ROOT_TAG + "/" + DEFAULT_SERVICE_TAG ).callMethod("defaultService").usingElementBodyAsArgument();
+
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG ).createObject().ofType( Application.class ).then().setNext( "addApplication" );
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + ROLE_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + NAME_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + VERSION_TAG ).createObject().ofType(Version.class).then().setBeanProperty().then().setNext("setVersion");
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + URL_TAG ).callMethod( "addUrl" ).usingElementBodyAsArgument();
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + PARAM_TAG ).createObject().ofType( Param.class ).then().addRule( paramRule ).then().setNext( "addParam" );
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + PARAM_TAG + "/" + NAME_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + PARAM_TAG + "/" + VALUE_TAG ).setBeanProperty();
+
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG ).createObject().ofType( Service.class ).then().setNext( "addService" );
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + ROLE_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + NAME_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + VERSION_TAG ).createObject().ofType(Version.class).then().setBeanProperty().then().setNext("setVersion");
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + URL_TAG ).callMethod( "addUrl" ).usingElementBodyAsArgument();
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + PARAM_TAG ).createObject().ofType( Param.class ).then().addRule( paramRule ).then().setNext( "addParam" );
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + PARAM_TAG + "/" + NAME_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + SERVICE_TAG + "/" + PARAM_TAG + "/" + VALUE_TAG ).setBeanProperty();
+
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG ).createObject().ofType( Provider.class ).then().setNext( "addProvider" );
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + ROLE_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + ENABLED_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + NAME_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + PARAM_TAG ).createObject().ofType( Param.class ).then().addRule( paramRule ).then().setNext( "addParam" );
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + PARAM_TAG + "/" + NAME_TAG ).setBeanProperty();
+ forPattern( ROOT_TAG + "/" + PROVIDER_TAG + "/" + PARAM_TAG + "/" + VALUE_TAG ).setBeanProperty();
+ }
+
+ private static class ParamRule extends Rule {
+
+ @Override
+ public void begin( String namespace, String name, Attributes attributes ) {
+ Param param = getDigester().peek();
+ String paramName = attributes.getValue( "name" );
+ if( paramName != null ) {
+ param.setName( paramName );
+ param.setValue( attributes.getValue( "value" ) );
+ }
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-server/src/test/java/org/apache/knox/gateway/GatewayFilterTest.java
----------------------------------------------------------------------
diff --cc gateway-server/src/test/java/org/apache/knox/gateway/GatewayFilterTest.java
index 178ff5e,0000000..ac22400
mode 100644,000000..100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/GatewayFilterTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/GatewayFilterTest.java
@@@ -1,171 -1,0 +1,220 @@@
+/**
+ * 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;
+
+import org.apache.knox.gateway.audit.api.AuditServiceFactory;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.filter.AbstractGatewayFilter;
++import org.apache.knox.gateway.topology.Topology;
+import org.apache.hadoop.test.category.FastTests;
+import org.apache.hadoop.test.category.UnitTests;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ *
+ */
+@Category( { UnitTests.class, FastTests.class } )
+public class GatewayFilterTest {
+
+ @Before
+ public void setup() {
+ AuditServiceFactory.getAuditService().createContext();
+ }
+
+ @After
+ public void reset() {
+ AuditServiceFactory.getAuditService().detachContext();
+ }
+
+ @Test
+ public void testNoFilters() throws ServletException, IOException {
+
+ FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
+ EasyMock.replay( config );
+
+ HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
+ ServletContext context = EasyMock.createNiceMock( ServletContext.class );
+ GatewayConfig gatewayConfig = EasyMock.createNiceMock( GatewayConfig.class );
+ EasyMock.expect( request.getPathInfo() ).andReturn( "source" ).anyTimes();
+ EasyMock.expect( request.getServletContext() ).andReturn( context ).anyTimes();
+ EasyMock.expect( context.getAttribute(
+ GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
+ EasyMock.expect(gatewayConfig.getHeaderNameForRemoteAddress()).andReturn(
+ "Custom-Forwarded-For").anyTimes();
+ EasyMock.replay( request );
+ EasyMock.replay( context );
+ EasyMock.replay( gatewayConfig );
+
+ HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
+ EasyMock.replay( response );
+
+ FilterChain chain = EasyMock.createNiceMock( FilterChain.class );
+ EasyMock.replay( chain );
+
+ GatewayFilter gateway = new GatewayFilter();
+ gateway.init( config );
+ gateway.doFilter( request, response, chain );
+ gateway.destroy();
+ }
+
+ @Test
+ public void testNoopFilter() throws ServletException, IOException, URISyntaxException {
+
+ FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
+ EasyMock.replay( config );
+
+ HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
+ ServletContext context = EasyMock.createNiceMock( ServletContext.class );
+ GatewayConfig gatewayConfig = EasyMock.createNiceMock( GatewayConfig.class );
+ EasyMock.expect( request.getPathInfo() ).andReturn( "source" ).anyTimes();
+ EasyMock.expect( request.getServletContext() ).andReturn( context ).anyTimes();
+ EasyMock.expect( context.getAttribute(
+ GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
+ EasyMock.expect(gatewayConfig.getHeaderNameForRemoteAddress()).andReturn(
+ "Custom-Forwarded-For").anyTimes();
+ EasyMock.replay( request );
+ EasyMock.replay( context );
+ EasyMock.replay( gatewayConfig );
+
+ HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
+ EasyMock.replay( response );
+
+ FilterChain chain = EasyMock.createNiceMock( FilterChain.class );
+ EasyMock.replay( chain );
+
+ Filter filter = EasyMock.createNiceMock( Filter.class );
+ EasyMock.replay( filter );
+
+ GatewayFilter gateway = new GatewayFilter();
+ gateway.addFilter( "path", "filter", filter, null, null );
+ gateway.init( config );
+ gateway.doFilter( request, response, chain );
+ gateway.destroy();
+
+ }
+
+ public static class TestRoleFilter extends AbstractGatewayFilter {
+
+ public Object role;
++ public String defaultServicePath;
++ public String url;
+
+ @Override
+ protected void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain chain ) throws IOException, ServletException {
+ this.role = request.getAttribute( AbstractGatewayFilter.TARGET_SERVICE_ROLE );
++ Topology topology = (Topology)request.getServletContext().getAttribute( "org.apache.knox.gateway.topology" );
++ if (topology != null) {
++ this.defaultServicePath = (String) topology.getDefaultServicePath();
++ url = new String(request.getRequestURL());
++ }
+ }
+
+ }
+
+ @Test
+ public void testTargetServiceRoleRequestAttribute() throws Exception {
+
+ FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
+ EasyMock.replay( config );
+
+ HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
+ ServletContext context = EasyMock.createNiceMock( ServletContext.class );
+ GatewayConfig gatewayConfig = EasyMock.createNiceMock( GatewayConfig.class );
+ EasyMock.expect( request.getPathInfo() ).andReturn( "test-path/test-resource" ).anyTimes();
+ EasyMock.expect( request.getServletContext() ).andReturn( context ).anyTimes();
+ EasyMock.expect( context.getAttribute(
+ GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
+ EasyMock.expect(gatewayConfig.getHeaderNameForRemoteAddress()).andReturn(
+ "Custom-Forwarded-For").anyTimes();
+ request.setAttribute( AbstractGatewayFilter.TARGET_SERVICE_ROLE, "test-role" );
+ EasyMock.expectLastCall().anyTimes();
+ EasyMock.expect( request.getAttribute( AbstractGatewayFilter.TARGET_SERVICE_ROLE ) ).andReturn( "test-role" ).anyTimes();
+ EasyMock.replay( request );
+ EasyMock.replay( context );
+ EasyMock.replay( gatewayConfig );
+
+ HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
+ EasyMock.replay( response );
+
+ TestRoleFilter filter = new TestRoleFilter();
+
+ GatewayFilter gateway = new GatewayFilter();
+ gateway.addFilter( "test-path/**", "test-filter", filter, null, "test-role" );
+ gateway.init( config );
+ gateway.doFilter( request, response );
+ gateway.destroy();
+
+ assertThat( (String)filter.role, is( "test-role" ) );
+
+ }
+
++ @Test
++ public void testDefaultServicePathTopologyRequestAttribute() throws Exception {
++
++ FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
++ EasyMock.replay( config );
++
++ Topology topology = EasyMock.createNiceMock( Topology.class );
++ topology.setDefaultServicePath("test-role/");
++ HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
++ ServletContext context = EasyMock.createNiceMock( ServletContext.class );
++ GatewayConfig gatewayConfig = EasyMock.createNiceMock( GatewayConfig.class );
++ EasyMock.expect( topology.getDefaultServicePath() ).andReturn( "test-role" ).anyTimes();
++ EasyMock.expect( request.getPathInfo() ).andReturn( "/test-path/test-resource" ).anyTimes();
++ EasyMock.expect( request.getServletContext() ).andReturn( context ).anyTimes();
++ EasyMock.expect( context.getAttribute(
++ GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
++ EasyMock.expect(gatewayConfig.getHeaderNameForRemoteAddress()).andReturn(
++ "Custom-Forwarded-For").anyTimes();
++ EasyMock.expect( request.getRequestURL() ).andReturn( new StringBuffer("http://host:8443/gateway/sandbox/test-path/test-resource/") ).anyTimes();
++
++ EasyMock.expect( context.getAttribute( "org.apache.hadoop.gateway.topology" ) ).andReturn( topology ).anyTimes();
++ EasyMock.replay( request );
++ EasyMock.replay( context );
++ EasyMock.replay( topology );
++ EasyMock.replay( gatewayConfig );
++
++ HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
++ EasyMock.replay( response );
++
++ TestRoleFilter filter = new TestRoleFilter();
++
++ GatewayFilter gateway = new GatewayFilter();
++ gateway.addFilter( "test-role/**/**", "test-filter", filter, null, "test-role" );
++ gateway.init( config );
++ gateway.doFilter( request, response );
++ gateway.destroy();
++
++ assertThat( (String)filter.defaultServicePath, is( "test-role" ) );
++ assertThat( (String)filter.url, is("http://host:8443/gateway/sandbox/test-role/test-path/test-resource"));
++
++ }
+}
http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
----------------------------------------------------------------------
diff --cc gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
index d28ad7f,0000000..95d6f9d
mode 100644,000000..100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
@@@ -1,266 -1,0 +1,610 @@@
+/**
+ * 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;
+
+import org.apache.commons.io.FileUtils;
++import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
++import org.apache.commons.io.monitor.FileAlterationListener;
+import org.apache.commons.io.monitor.FileAlterationMonitor;
+import org.apache.commons.io.monitor.FileAlterationObserver;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.topology.impl.DefaultTopologyService;
++import org.apache.knox.gateway.config.GatewayConfig;
++import org.apache.knox.gateway.services.security.AliasService;
++import org.apache.knox.gateway.services.topology.impl.DefaultTopologyService;
++import org.apache.knox.gateway.topology.*;
+import org.apache.hadoop.test.TestUtils;
+import org.apache.knox.gateway.topology.Param;
+import org.apache.knox.gateway.topology.Provider;
+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.services.security.AliasService;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
- import java.util.*;
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.Collection;
++import java.util.HashMap;
++import java.util.HashSet;
++import java.util.Iterator;
++import java.util.List;
++import java.util.Map;
++import java.util.Set;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.core.IsNull.notNullValue;
++import static org.junit.Assert.assertEquals;
++import static org.junit.Assert.assertFalse;
++import static org.junit.Assert.assertNotEquals;
++import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class DefaultTopologyServiceTest {
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ private File createDir() throws IOException {
+ return TestUtils.createTempDir(this.getClass().getSimpleName() + "-");
+ }
+
+ private File createFile(File parent, String name, String resource, long timestamp) throws IOException {
+ File file = new File(parent, name);
+ if (!file.exists()) {
+ FileUtils.touch(file);
+ }
+ InputStream input = ClassLoader.getSystemResourceAsStream(resource);
+ OutputStream output = FileUtils.openOutputStream(file);
+ IOUtils.copy(input, output);
+ //KNOX-685: output.flush();
+ input.close();
+ output.close();
+ file.setLastModified(timestamp);
+ assertTrue("Failed to create test file " + file.getAbsolutePath(), file.exists());
+ assertTrue("Failed to populate test file " + file.getAbsolutePath(), file.length() > 0);
+
+ return file;
+ }
+
+ @Test
+ public void testGetTopologies() throws Exception {
+
+ File dir = createDir();
+ File topologyDir = new File(dir, "topologies");
+
- File descriptorsDir = new File(dir, "descriptors");
- descriptorsDir.mkdirs();
-
- File sharedProvidersDir = new File(dir, "shared-providers");
- sharedProvidersDir.mkdirs();
-
+ long time = topologyDir.lastModified();
+ try {
+ createFile(topologyDir, "one.xml", "org/apache/knox/gateway/topology/file/topology-one.xml", time);
+
+ TestTopologyListener topoListener = new TestTopologyListener();
+ FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
+
+ TopologyService provider = new DefaultTopologyService();
+ Map<String, String> c = new HashMap<>();
+
+ GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+ EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
- EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
++ EasyMock.expect(config.getGatewayConfDir()).andReturn(topologyDir.getParentFile().getAbsolutePath()).anyTimes();
+ EasyMock.replay(config);
+
+ provider.init(config, c);
+
+ provider.addTopologyChangeListener(topoListener);
+
+ provider.reloadTopologies();
+
+ Collection<Topology> topologies = provider.getTopologies();
+ assertThat(topologies, notNullValue());
+ assertThat(topologies.size(), is(1));
+ Topology topology = topologies.iterator().next();
+ assertThat(topology.getName(), is("one"));
+ assertThat(topology.getTimestamp(), is(time));
+ assertThat(topoListener.events.size(), is(1));
+ topoListener.events.clear();
+
+ // Add a file to the directory.
+ File two = createFile(topologyDir, "two.xml",
+ "org/apache/knox/gateway/topology/file/topology-two.xml", 1L);
+ provider.reloadTopologies();
+ topologies = provider.getTopologies();
+ assertThat(topologies.size(), is(2));
+ Set<String> names = new HashSet<>(Arrays.asList("one", "two"));
+ Iterator<Topology> iterator = topologies.iterator();
+ topology = iterator.next();
+ assertThat(names, hasItem(topology.getName()));
+ names.remove(topology.getName());
+ topology = iterator.next();
+ assertThat(names, hasItem(topology.getName()));
+ names.remove(topology.getName());
+ assertThat(names.size(), is(0));
+ assertThat(topoListener.events.size(), is(1));
+ List<TopologyEvent> events = topoListener.events.get(0);
+ assertThat(events.size(), is(1));
+ TopologyEvent event = events.get(0);
+ assertThat(event.getType(), is(TopologyEvent.Type.CREATED));
+ assertThat(event.getTopology(), notNullValue());
+
+ // Update a file in the directory.
+ two = createFile(topologyDir, "two.xml",
+ "org/apache/knox/gateway/topology/file/topology-three.xml", 2L);
+ provider.reloadTopologies();
+ topologies = provider.getTopologies();
+ assertThat(topologies.size(), is(2));
+ names = new HashSet<>(Arrays.asList("one", "two"));
+ iterator = topologies.iterator();
+ topology = iterator.next();
+ assertThat(names, hasItem(topology.getName()));
+ names.remove(topology.getName());
+ topology = iterator.next();
+ assertThat(names, hasItem(topology.getName()));
+ names.remove(topology.getName());
+ assertThat(names.size(), is(0));
+
+ // Remove a file from the directory.
+ two.delete();
+ provider.reloadTopologies();
+ topologies = provider.getTopologies();
+ assertThat(topologies.size(), is(1));
+ topology = topologies.iterator().next();
+ assertThat(topology.getName(), is("one"));
+ assertThat(topology.getTimestamp(), is(time));
+
++ } finally {
++ FileUtils.deleteQuietly(dir);
++ }
++ }
++
++ /**
++ * KNOX-1014
++ *
++ * Test the lifecycle relationship between simple descriptors and topology files.
++ *
++ * N.B. This test depends on the DummyServiceDiscovery extension being configured:
++ * org.apache.hadoop.gateway.topology.discovery.test.extension.DummyServiceDiscovery
++ */
++ @Test
++ public void testSimpleDescriptorsTopologyGeneration() throws Exception {
++
++ File dir = createDir();
++ File topologyDir = new File(dir, "topologies");
++ topologyDir.mkdirs();
++
++ File descriptorsDir = new File(dir, "descriptors");
++ descriptorsDir.mkdirs();
++
++ File sharedProvidersDir = new File(dir, "shared-providers");
++ sharedProvidersDir.mkdirs();
++
++ try {
++ TestTopologyListener topoListener = new TestTopologyListener();
++ FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
++
++ TopologyService provider = new DefaultTopologyService();
++ Map<String, String> c = new HashMap<>();
++
++ GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
++ EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
++ EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
++ EasyMock.replay(config);
++
++ provider.init(config, c);
++ provider.addTopologyChangeListener(topoListener);
++ provider.reloadTopologies();
++
++
+ // Add a simple descriptor to the descriptors dir to verify topology generation and loading (KNOX-1006)
- // N.B. This part of the test depends on the DummyServiceDiscovery extension being configured:
- // org.apache.knox.gateway.topology.discovery.test.extension.DummyServiceDiscovery
+ AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+ EasyMock.expect(aliasService.getPasswordFromAliasForGateway(anyObject(String.class))).andReturn(null).anyTimes();
+ EasyMock.replay(aliasService);
+ DefaultTopologyService.DescriptorsMonitor dm =
- new DefaultTopologyService.DescriptorsMonitor(topologyDir, aliasService);
++ new DefaultTopologyService.DescriptorsMonitor(topologyDir, aliasService);
++
++ // Listener to simulate the topologies directory monitor, to notice when a topology has been deleted
++ provider.addTopologyChangeListener(new TestTopologyDeleteListener((DefaultTopologyService)provider));
+
+ // Write out the referenced provider config first
+ File provCfgFile = createFile(sharedProvidersDir,
+ "ambari-cluster-policy.xml",
- "org/apache/knox/gateway/topology/file/ambari-cluster-policy.xml",
- 1L);
++ "org/apache/knox/gateway/topology/file/ambari-cluster-policy.xml",
++ System.currentTimeMillis());
+ try {
+ // Create the simple descriptor in the descriptors dir
- File simpleDesc =
- createFile(descriptorsDir,
- "four.json",
- "org/apache/knox/gateway/topology/file/simple-topology-four.json",
- 1L);
++ File simpleDesc = createFile(descriptorsDir,
++ "four.json",
++ "org/apache/knox/gateway/topology/file/simple-topology-four.json",
++ System.currentTimeMillis());
+
+ // Trigger the topology generation by noticing the simple descriptor
+ dm.onFileChange(simpleDesc);
+
+ // Load the generated topology
+ provider.reloadTopologies();
++ Collection<Topology> topologies = provider.getTopologies();
++ assertThat(topologies.size(), is(1));
++ Iterator<Topology> iterator = topologies.iterator();
++ Topology topology = iterator.next();
++ assertThat("four", is(topology.getName()));
++ int serviceCount = topology.getServices().size();
++ assertEquals("Expected the same number of services as are declared in the simple dscriptor.", 10, serviceCount);
++
++ // Overwrite the simple descriptor with a different set of services, and check that the changes are
++ // propagated to the associated topology
++ simpleDesc = createFile(descriptorsDir,
++ "four.json",
++ "org/apache/knox/gateway/topology/file/simple-descriptor-five.json",
++ System.currentTimeMillis());
++ dm.onFileChange(simpleDesc);
++ provider.reloadTopologies();
++ topologies = provider.getTopologies();
++ topology = topologies.iterator().next();
++ assertNotEquals(serviceCount, topology.getServices().size());
++ assertEquals(6, topology.getServices().size());
++
++ // Delete the simple descriptor, and make sure that the associated topology file is deleted
++ simpleDesc.delete();
++ dm.onFileDelete(simpleDesc);
++ provider.reloadTopologies();
+ topologies = provider.getTopologies();
- assertThat(topologies.size(), is(2));
- names = new HashSet<>(Arrays.asList("one", "four"));
- iterator = topologies.iterator();
- topology = iterator.next();
- assertThat(names, hasItem(topology.getName()));
- names.remove(topology.getName());
- topology = iterator.next();
- assertThat(names, hasItem(topology.getName()));
- names.remove(topology.getName());
- assertThat(names.size(), is(0));
++ assertTrue(topologies.isEmpty());
++
++ // Delete a topology file, and make sure that the associated simple descriptor is deleted
++ // Overwrite the simple descriptor with a different set of services, and check that the changes are
++ // propagated to the associated topology
++ simpleDesc = createFile(descriptorsDir,
++ "deleteme.json",
++ "org/apache/knox/gateway/topology/file/simple-descriptor-five.json",
++ System.currentTimeMillis());
++ dm.onFileChange(simpleDesc);
++ provider.reloadTopologies();
++ topologies = provider.getTopologies();
++ assertFalse(topologies.isEmpty());
++ topology = topologies.iterator().next();
++ assertEquals("deleteme", topology.getName());
++ File topologyFile = new File(topologyDir, topology.getName() + ".xml");
++ assertTrue(topologyFile.exists());
++ topologyFile.delete();
++ provider.reloadTopologies();
++ assertFalse("Simple descriptor should have been deleted because the associated topology was.",
++ simpleDesc.exists());
++
+ } finally {
+ provCfgFile.delete();
-
+ }
+ } finally {
+ FileUtils.deleteQuietly(dir);
+ }
+ }
+
++ /**
++ * KNOX-1014
++ *
++ * Test the lifecycle relationship between provider configuration files, simple descriptors, and topology files.
++ *
++ * N.B. This test depends on the DummyServiceDiscovery extension being configured:
++ * org.apache.hadoop.gateway.topology.discovery.test.extension.DummyServiceDiscovery
++ */
++ @Test
++ public void testTopologiesUpdateFromProviderConfigChange() throws Exception {
++ File dir = createDir();
++ File topologyDir = new File(dir, "topologies");
++ topologyDir.mkdirs();
++
++ File descriptorsDir = new File(dir, "descriptors");
++ descriptorsDir.mkdirs();
++
++ File sharedProvidersDir = new File(dir, "shared-providers");
++ sharedProvidersDir.mkdirs();
++
++ try {
++ TestTopologyListener topoListener = new TestTopologyListener();
++ FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
++
++ TopologyService ts = new DefaultTopologyService();
++ Map<String, String> c = new HashMap<>();
++
++ GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
++ EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
++ EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
++ EasyMock.replay(config);
++
++ ts.init(config, c);
++ ts.addTopologyChangeListener(topoListener);
++ ts.reloadTopologies();
++
++ java.lang.reflect.Field dmField = ts.getClass().getDeclaredField("descriptorsMonitor");
++ dmField.setAccessible(true);
++ DefaultTopologyService.DescriptorsMonitor dm = (DefaultTopologyService.DescriptorsMonitor) dmField.get(ts);
++
++ // Write out the referenced provider configs first
++ createFile(sharedProvidersDir,
++ "provider-config-one.xml",
++ "org/apache/knox/gateway/topology/file/provider-config-one.xml",
++ System.currentTimeMillis());
++
++ // Create the simple descriptor, which depends on provider-config-one.xml
++ File simpleDesc = createFile(descriptorsDir,
++ "six.json",
++ "org/apache/knox/gateway/topology/file/simple-descriptor-six.json",
++ System.currentTimeMillis());
++
++ // "Notice" the simple descriptor change, and generate a topology based on it
++ dm.onFileChange(simpleDesc);
++
++ // Load the generated topology
++ ts.reloadTopologies();
++ Collection<Topology> topologies = ts.getTopologies();
++ assertThat(topologies.size(), is(1));
++ Iterator<Topology> iterator = topologies.iterator();
++ Topology topology = iterator.next();
++ assertFalse("The Shiro provider is disabled in provider-config-one.xml",
++ topology.getProvider("authentication", "ShiroProvider").isEnabled());
++
++ // Overwrite the referenced provider configuration with a different ShiroProvider config, and check that the
++ // changes are propagated to the associated topology
++ File providerConfig = createFile(sharedProvidersDir,
++ "provider-config-one.xml",
++ "org/apache/knox/gateway/topology/file/ambari-cluster-policy.xml",
++ System.currentTimeMillis());
++
++ // "Notice" the simple descriptor change as a result of the referenced config change
++ dm.onFileChange(simpleDesc);
++
++ // Load the generated topology
++ ts.reloadTopologies();
++ topologies = ts.getTopologies();
++ assertFalse(topologies.isEmpty());
++ topology = topologies.iterator().next();
++ assertTrue("The Shiro provider is enabled in ambari-cluster-policy.xml",
++ topology.getProvider("authentication", "ShiroProvider").isEnabled());
++
++ // Delete the provider configuration, and make sure that the associated topology file is unaffected.
++ // The topology file should not be affected because the simple descriptor handling will fail to resolve the
++ // referenced provider configuration.
++ providerConfig.delete(); // Delete the file
++ dm.onFileChange(simpleDesc); // The provider config deletion will trigger a descriptor change notification
++ ts.reloadTopologies();
++ topologies = ts.getTopologies();
++ assertFalse(topologies.isEmpty());
++ assertTrue("The Shiro provider is enabled in ambari-cluster-policy.xml",
++ topology.getProvider("authentication", "ShiroProvider").isEnabled());
++
++ } finally {
++ FileUtils.deleteQuietly(dir);
++ }
++ }
++
++ /**
++ * KNOX-1039
++ */
++ @Test
++ public void testConfigurationCRUDAPI() throws Exception {
++ File dir = createDir();
++ File topologyDir = new File(dir, "topologies");
++ topologyDir.mkdirs();
++
++ File descriptorsDir = new File(dir, "descriptors");
++ descriptorsDir.mkdirs();
++
++ File sharedProvidersDir = new File(dir, "shared-providers");
++ sharedProvidersDir.mkdirs();
++
++ try {
++ TestTopologyListener topoListener = new TestTopologyListener();
++ FileAlterationMonitor monitor = new FileAlterationMonitor(Long.MAX_VALUE);
++
++ TopologyService ts = new DefaultTopologyService();
++ Map<String, String> c = new HashMap<>();
++
++ GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
++ EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
++ EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
++ EasyMock.replay(config);
++
++ ts.init(config, c);
++ ts.addTopologyChangeListener(topoListener);
++ ts.reloadTopologies();
++
++ java.lang.reflect.Field dmField = ts.getClass().getDeclaredField("descriptorsMonitor");
++ dmField.setAccessible(true);
++ DefaultTopologyService.DescriptorsMonitor dm = (DefaultTopologyService.DescriptorsMonitor) dmField.get(ts);
++
++ final String simpleDescName = "six.json";
++ final String provConfOne = "provider-config-one.xml";
++ final String provConfTwo = "ambari-cluster-policy.xml";
++
++ // "Deploy" the referenced provider configs first
++ boolean isDeployed =
++ ts.deployProviderConfiguration(provConfOne,
++ FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/provider-config-one.xml").toURI())));
++ assertTrue(isDeployed);
++ File provConfOneFile = new File(sharedProvidersDir, provConfOne);
++ assertTrue(provConfOneFile.exists());
++
++ isDeployed =
++ ts.deployProviderConfiguration(provConfTwo,
++ FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/ambari-cluster-policy.xml").toURI())));
++ assertTrue(isDeployed);
++ File provConfTwoFile = new File(sharedProvidersDir, provConfTwo);
++ assertTrue(provConfTwoFile.exists());
++
++ // Validate the provider configurations known by the topology service
++ Collection<File> providerConfigurations = ts.getProviderConfigurations();
++ assertNotNull(providerConfigurations);
++ assertEquals(2, providerConfigurations.size());
++ assertTrue(providerConfigurations.contains(provConfOneFile));
++ assertTrue(providerConfigurations.contains(provConfTwoFile));
++
++ // "Deploy" the simple descriptor, which depends on provConfOne
++ isDeployed =
++ ts.deployDescriptor(simpleDescName,
++ FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/simple-descriptor-six.json").toURI())));
++ assertTrue(isDeployed);
++ File simpleDesc = new File(descriptorsDir, simpleDescName);
++ assertTrue(simpleDesc.exists());
++
++ // Validate the simple descriptors known by the topology service
++ Collection<File> descriptors = ts.getDescriptors();
++ assertNotNull(descriptors);
++ assertEquals(1, descriptors.size());
++ assertTrue(descriptors.contains(simpleDesc));
++
++ // "Notice" the simple descriptor, so the provider configuration dependency relationship is recorded
++ dm.onFileChange(simpleDesc);
++
++ // Attempt to delete the referenced provConfOne
++ assertFalse("Should not be able to delete a provider configuration that is referenced by one or more descriptors",
++ ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfOne)));
++
++ // Overwrite the simple descriptor with content that changes the provider config reference to provConfTwo
++ isDeployed =
++ ts.deployDescriptor(simpleDescName,
++ FileUtils.readFileToString(new File(ClassLoader.getSystemResource("org/apache/hadoop/gateway/topology/file/simple-descriptor-five.json").toURI())));
++ assertTrue(isDeployed);
++ assertTrue(simpleDesc.exists());
++ ts.getProviderConfigurations();
++
++ // "Notice" the simple descriptor, so the provider configuration dependency relationship is updated
++ dm.onFileChange(simpleDesc);
++
++ // Attempt to delete the referenced provConfOne
++ assertTrue("Should be able to delete the provider configuration, now that it's not referenced by any descriptors",
++ ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfOne)));
++
++ // Re-validate the provider configurations known by the topology service
++ providerConfigurations = ts.getProviderConfigurations();
++ assertNotNull(providerConfigurations);
++ assertEquals(1, providerConfigurations.size());
++ assertFalse(providerConfigurations.contains(provConfOneFile));
++ assertTrue(providerConfigurations.contains(provConfTwoFile));
++
++ // Attempt to delete the referenced provConfTwo
++ assertFalse("Should not be able to delete a provider configuration that is referenced by one or more descriptors",
++ ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfTwo)));
++
++ // Delete the referencing simple descriptor
++ assertTrue(ts.deleteDescriptor(FilenameUtils.getBaseName(simpleDescName)));
++ assertFalse(simpleDesc.exists());
++
++ // Re-validate the simple descriptors known by the topology service
++ descriptors = ts.getDescriptors();
++ assertNotNull(descriptors);
++ assertTrue(descriptors.isEmpty());
++
++ // "Notice" the simple descriptor, so the provider configuration dependency relationship is updated
++ dm.onFileDelete(simpleDesc);
++
++ // Attempt to delete the referenced provConfTwo
++ assertTrue("Should be able to delete the provider configuration, now that it's not referenced by any descriptors",
++ ts.deleteProviderConfiguration(FilenameUtils.getBaseName(provConfTwo)));
++
++ // Re-validate the provider configurations known by the topology service
++ providerConfigurations = ts.getProviderConfigurations();
++ assertNotNull(providerConfigurations);
++ assertTrue(providerConfigurations.isEmpty());
++
++ } finally {
++ FileUtils.deleteQuietly(dir);
++ }
++ }
++
+ private void kickMonitor(FileAlterationMonitor monitor) {
+ for (FileAlterationObserver observer : monitor.getObservers()) {
+ observer.checkAndNotify();
+ }
+ }
+
++
+ @Test
+ public void testProviderParamsOrderIsPreserved() {
+
+ Provider provider = new Provider();
+ String names[] = {"ldapRealm=",
+ "ldapContextFactory",
+ "ldapRealm.contextFactory",
+ "ldapGroupRealm",
+ "ldapGroupRealm.contextFactory",
+ "ldapGroupRealm.contextFactory.systemAuthenticationMechanism"
+ };
+
+ Param param = null;
+ for (String name : names) {
+ param = new Param();
+ param.setName(name);
+ param.setValue(name);
+ provider.addParam(param);
+
+ }
+ Map<String, String> params = provider.getParams();
+ Set<String> keySet = params.keySet();
+ Iterator<String> iter = keySet.iterator();
+ int i = 0;
+ while (iter.hasNext()) {
+ assertTrue(iter.next().equals(names[i++]));
+ }
+
+ }
+
+ private class TestTopologyListener implements TopologyListener {
+
- public ArrayList<List<TopologyEvent>> events = new ArrayList<List<TopologyEvent>>();
++ ArrayList<List<TopologyEvent>> events = new ArrayList<List<TopologyEvent>>();
+
+ @Override
+ public void handleTopologyEvent(List<TopologyEvent> events) {
+ this.events.add(events);
+ }
+
+ }
+
++
++ private class TestTopologyDeleteListener implements TopologyListener {
++
++ FileAlterationListener delegate;
++
++ TestTopologyDeleteListener(FileAlterationListener delegate) {
++ this.delegate = delegate;
++ }
++
++ @Override
++ public void handleTopologyEvent(List<TopologyEvent> events) {
++ for (TopologyEvent event : events) {
++ if (event.getType().equals(TopologyEvent.Type.DELETED)) {
++ delegate.onFileDelete(new File(event.getTopology().getUri()));
++ }
++ }
++ }
++
++ }
++
+}