NIFIREG-238: Allow aliasing of registry url
NIFIREG-238: PR Feedback
PR Feedback - Unique constraint on internal token
PR Feedback - Using map to store aliases
Updating default properties file

This closes #162.

Signed-off-by: Bryan Bende <bbende@apache.org>
diff --git a/nifi-registry-assembly/pom.xml b/nifi-registry-assembly/pom.xml
index f1cc1ac..34892b3 100644
--- a/nifi-registry-assembly/pom.xml
+++ b/nifi-registry-assembly/pom.xml
@@ -160,6 +160,9 @@
         <!-- nifi-registry.properties: provider properties -->
         <nifi.registry.providers.configuration.file>./conf/providers.xml</nifi.registry.providers.configuration.file>
 
+        <!-- nifi-registry.properties: registry alias properties -->
+        <nifi.registry.registry.alias.configuration.file>./conf/registry-aliases.xml</nifi.registry.registry.alias.configuration.file>
+
         <!-- nifi-registry.properties: extension properties -->
         <nifi.registry.extensions.working.directory>./work/extensions</nifi.registry.extensions.working.directory>
 
diff --git a/nifi-registry-core/nifi-registry-framework/pom.xml b/nifi-registry-core/nifi-registry-framework/pom.xml
index 5996326..f5f1d65 100644
--- a/nifi-registry-core/nifi-registry-framework/pom.xml
+++ b/nifi-registry-core/nifi-registry-framework/pom.xml
@@ -38,6 +38,19 @@
                 <artifactId>jaxb2-maven-plugin</artifactId>
                 <executions>
                     <execution>
+                        <id>aliases</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/main/xsd/aliases.xsd</source>
+                            </sources>
+                            <packageName>org.apache.nifi.registry.url.aliaser.generated</packageName>
+                            <clearOutputDir>false</clearOutputDir>
+                        </configuration>
+                    </execution>
+                    <execution>
                         <id>providers</id>
                         <goals>
                             <goal>xjc</goal>
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
index 1e074bf..bbf647b 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
@@ -60,6 +60,7 @@
 import org.apache.nifi.registry.flow.diff.StandardFlowComparator;
 import org.apache.nifi.registry.provider.flow.StandardFlowSnapshotContext;
 import org.apache.nifi.registry.serialization.Serializer;
+import org.apache.nifi.registry.service.alias.RegistryUrlAliasService;
 import org.apache.nifi.registry.service.extension.ExtensionService;
 import org.apache.nifi.registry.service.mapper.BucketMappings;
 import org.apache.nifi.registry.service.mapper.ExtensionMappings;
@@ -109,6 +110,7 @@
     private final Serializer<VersionedProcessGroup> processGroupSerializer;
     private final ExtensionService extensionService;
     private final Validator validator;
+    private final RegistryUrlAliasService registryUrlAliasService;
 
     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     private final Lock readLock = lock.readLock();
@@ -119,17 +121,14 @@
                            final FlowPersistenceProvider flowPersistenceProvider,
                            final Serializer<VersionedProcessGroup> processGroupSerializer,
                            final ExtensionService extensionService,
-                           final Validator validator) {
-        this.metadataService = metadataService;
-        this.flowPersistenceProvider = flowPersistenceProvider;
-        this.processGroupSerializer = processGroupSerializer;
-        this.extensionService = extensionService;
-        this.validator = validator;
-        Validate.notNull(this.metadataService);
-        Validate.notNull(this.flowPersistenceProvider);
-        Validate.notNull(this.processGroupSerializer);
-        Validate.notNull(this.extensionService);
-        Validate.notNull(this.validator);
+                           final Validator validator,
+                           final RegistryUrlAliasService registryUrlAliasService) {
+        this.metadataService = Validate.notNull(metadataService);
+        this.flowPersistenceProvider = Validate.notNull(flowPersistenceProvider);
+        this.processGroupSerializer = Validate.notNull(processGroupSerializer);
+        this.extensionService = Validate.notNull(extensionService);
+        this.validator = Validate.notNull(validator);
+        this.registryUrlAliasService = Validate.notNull(registryUrlAliasService);
     }
 
     private <T>  void validate(T t, String invalidMessage) {
@@ -672,6 +671,7 @@
 
             // serialize the snapshot
             final ByteArrayOutputStream out = new ByteArrayOutputStream();
+            registryUrlAliasService.setInternal(flowSnapshot.getFlowContents());
             processGroupSerializer.serialize(flowSnapshot.getFlowContents(), out);
 
             // save the serialized snapshot to the persistence provider
@@ -695,6 +695,7 @@
 
             flowSnapshot.setBucket(bucket);
             flowSnapshot.setFlow(updatedVersionedFlow);
+            registryUrlAliasService.setExternal(flowSnapshot.getFlowContents());
             return flowSnapshot;
         } finally {
             writeLock.unlock();
@@ -766,6 +767,7 @@
 
         // create the snapshot to return
         final VersionedFlowSnapshot snapshot = new VersionedFlowSnapshot();
+        registryUrlAliasService.setExternal(flowContents);
         snapshot.setFlowContents(flowContents);
         snapshot.setSnapshotMetadata(snapshotMetadata);
         snapshot.setFlow(versionedFlow);
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/alias/RegistryUrlAliasService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/alias/RegistryUrlAliasService.java
new file mode 100644
index 0000000..b444e83
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/alias/RegistryUrlAliasService.java
@@ -0,0 +1,171 @@
+/*
+ * 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.nifi.registry.service.alias;
+
+import org.apache.nifi.registry.flow.VersionedFlowCoordinates;
+import org.apache.nifi.registry.flow.VersionedProcessGroup;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.provider.ProviderFactoryException;
+import org.apache.nifi.registry.provider.StandardProviderFactory;
+import org.apache.nifi.registry.security.util.XmlUtils;
+import org.apache.nifi.registry.url.aliaser.generated.Alias;
+import org.apache.nifi.registry.url.aliaser.generated.Aliases;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.File;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Allows aliasing of registry url(s) without modifying the flows on disk.
+ */
+@Service
+public class RegistryUrlAliasService {
+    private static final String ALIASES_XSD = "/aliases.xsd";
+    private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.url.aliaser.generated";
+    private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();
+
+    /**
+     * Load the JAXBContext.
+     */
+    private static JAXBContext initializeJaxbContext() {
+        try {
+            return JAXBContext.newInstance(JAXB_GENERATED_PATH, RegistryUrlAliasService.class.getClassLoader());
+        } catch (JAXBException e) {
+            throw new RuntimeException("Unable to create JAXBContext.", e);
+        }
+    }
+
+    // Will be LinkedHashMap to preserve insertion order.
+    private final Map<String, String> aliases;
+
+    @Autowired
+    public RegistryUrlAliasService(NiFiRegistryProperties niFiRegistryProperties) {
+        this(createAliases(niFiRegistryProperties));
+    }
+
+    private static List<Alias> createAliases(NiFiRegistryProperties niFiRegistryProperties) {
+        File configurationFile = niFiRegistryProperties.getRegistryAliasConfigurationFile();
+        if (configurationFile.exists()) {
+            try {
+                // find the schema
+                final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+                final Schema schema = schemaFactory.newSchema(StandardProviderFactory.class.getResource(ALIASES_XSD));
+
+                // attempt to unmarshal
+                final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
+                unmarshaller.setSchema(schema);
+
+                final JAXBElement<Aliases> element = unmarshaller.unmarshal(XmlUtils.createSafeReader(new StreamSource(configurationFile)), Aliases.class);
+                return element.getValue().getAlias();
+            } catch (SAXException | JAXBException | XMLStreamException e) {
+                throw new ProviderFactoryException("Unable to load the registry alias configuration file at: " + configurationFile.getAbsolutePath(), e);
+            }
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    protected RegistryUrlAliasService(List<Alias> aliases) {
+        Pattern urlStart = Pattern.compile("^https?://");
+
+        this.aliases = new LinkedHashMap<>();
+
+        for (Alias alias : aliases) {
+            String internal = alias.getInternal();
+            String external = alias.getExternal();
+
+            if (!urlStart.matcher(external).find()) {
+                throw new IllegalArgumentException("Expected " + external + " to start with http:// or https://");
+            }
+
+            if (this.aliases.put(internal, external) != null) {
+                throw new IllegalArgumentException("Duplicate internal token " + internal);
+            }
+        }
+    }
+
+    /**
+     * Recursively replaces the aliases with the external url for a process group and children.
+     */
+    public void setExternal(VersionedProcessGroup processGroup) {
+        processGroup.getProcessGroups().forEach(this::setExternal);
+
+        VersionedFlowCoordinates coordinates = processGroup.getVersionedFlowCoordinates();
+        if (coordinates != null) {
+            coordinates.setRegistryUrl(getExternal(coordinates.getRegistryUrl()));
+        }
+    }
+
+    /**
+     * Recursively replaces the external url with the aliases for a process group and children.
+     */
+    public void setInternal(VersionedProcessGroup processGroup) {
+        processGroup.getProcessGroups().forEach(this::setInternal);
+
+        VersionedFlowCoordinates coordinates = processGroup.getVersionedFlowCoordinates();
+        if (coordinates != null) {
+            coordinates.setRegistryUrl(getInternal(coordinates.getRegistryUrl()));
+        }
+    }
+
+    protected String getExternal(String url) {
+        for (Map.Entry<String, String> alias : aliases.entrySet()) {
+            String internal = alias.getKey();
+            String external = alias.getValue();
+
+            if (url.startsWith(internal)) {
+                int internalLength = internal.length();
+                if (url.length() == internalLength) {
+                    return external;
+                }
+                return external + url.substring(internalLength);
+            }
+        }
+        return url;
+    }
+
+    protected String getInternal(String url) {
+        for (Map.Entry<String, String> alias : aliases.entrySet()) {
+            String internal = alias.getKey();
+            String external = alias.getValue();
+
+            if (url.startsWith(external)) {
+                int externalLength = external.length();
+                if (url.length() == externalLength) {
+                    return internal;
+                }
+                return internal + url.substring(externalLength);
+            }
+        }
+        return url;
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/xsd/aliases.xsd b/nifi-registry-core/nifi-registry-framework/src/main/xsd/aliases.xsd
new file mode 100644
index 0000000..70c9d4f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/aliases.xsd
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <!-- Provider type -->
+    <xs:complexType name="Alias">
+        <xs:sequence>
+            <xs:element name="internal" type="NonEmptyStringType"/>
+            <xs:element name="external" type="NonEmptyStringType"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:simpleType name="NonEmptyStringType">
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- providers -->
+    <xs:element name="aliases">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="alias" type="Alias" minOccurs="0" maxOccurs="unbounded" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+</xs:schema>
\ No newline at end of file
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
index 8f0de1b..8125f31 100644
--- a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
@@ -32,6 +32,7 @@
 import org.apache.nifi.registry.flow.VersionedProcessor;
 import org.apache.nifi.registry.serialization.Serializer;
 import org.apache.nifi.registry.serialization.VersionedProcessGroupSerializer;
+import org.apache.nifi.registry.service.alias.RegistryUrlAliasService;
 import org.apache.nifi.registry.service.extension.ExtensionService;
 import org.apache.nifi.registry.service.extension.StandardExtensionService;
 import org.junit.Assert;
@@ -77,6 +78,7 @@
     private Serializer<VersionedProcessGroup> snapshotSerializer;
     private ExtensionService extensionService;
     private Validator validator;
+    private RegistryUrlAliasService registryUrlAliasService;
 
     private RegistryService registryService;
 
@@ -86,11 +88,12 @@
         flowPersistenceProvider = mock(FlowPersistenceProvider.class);
         snapshotSerializer = mock(VersionedProcessGroupSerializer.class);
         extensionService = mock(StandardExtensionService.class);
+        registryUrlAliasService = mock(RegistryUrlAliasService.class);
 
         final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
         validator = validatorFactory.getValidator();
 
-        registryService = new RegistryService(metadataService, flowPersistenceProvider, snapshotSerializer, extensionService, validator);
+        registryService = new RegistryService(metadataService, flowPersistenceProvider, snapshotSerializer, extensionService, validator, registryUrlAliasService);
     }
 
     // ---------------------- Test Bucket methods ---------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/alias/RegistryUrlAliasServiceTest.java b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/alias/RegistryUrlAliasServiceTest.java
new file mode 100644
index 0000000..a1782c4
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/alias/RegistryUrlAliasServiceTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.nifi.registry.service.alias;
+
+import org.apache.nifi.registry.url.aliaser.generated.Alias;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+
+public class RegistryUrlAliasServiceTest {
+    private static Alias createAlias(String internal, String external) {
+        Alias result = new Alias();
+        result.setInternal(internal);
+        result.setExternal(external);
+        return result;
+    }
+
+    @Test
+    public void testNoAliases() {
+        RegistryUrlAliasService aliaser = new RegistryUrlAliasService(Collections.emptyList());
+
+        String url = "https://registry.com:18080";
+
+        assertEquals(url, aliaser.getExternal(url));
+        assertEquals(url, aliaser.getInternal(url));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMalformedExternal() {
+        new RegistryUrlAliasService(Collections.singletonList(createAlias("https://registry.com:18080", "registry.com:18080")));
+    }
+
+    @Test
+    public void testSingleAliasUrl() {
+        String internal = "https://registry-1.com:18443";
+        String external = "http://localhost:18080";
+        String unchanged = "https://registry-2.com:18443";
+
+        RegistryUrlAliasService aliaser = new RegistryUrlAliasService(Collections.singletonList(createAlias(internal, external)));
+
+        assertEquals(external, aliaser.getExternal(internal));
+        assertEquals(internal, aliaser.getInternal(external));
+
+        assertEquals(unchanged, aliaser.getExternal(unchanged));
+        assertEquals(unchanged, aliaser.getInternal(unchanged));
+
+        // Ensure replacement is only the prefix
+        internal += "/nifi-registry/";
+        external += "/nifi-registry/";
+        unchanged += "/nifi-registry/";
+
+        assertEquals(external, aliaser.getExternal(internal));
+        assertEquals(internal, aliaser.getInternal(external));
+
+        assertEquals(unchanged, aliaser.getExternal(unchanged));
+        assertEquals(unchanged, aliaser.getInternal(unchanged));
+    }
+
+    @Test
+    public void testSingleAliasToken() {
+        String internal = "THIS_NIFI_REGISTRY";
+        String external = "http://localhost:18080";
+        String unchanged = "https://registry-2.com:18443";
+
+        RegistryUrlAliasService aliaser = new RegistryUrlAliasService(Collections.singletonList(createAlias(internal, external)));
+
+        assertEquals(external, aliaser.getExternal(internal));
+        assertEquals(internal, aliaser.getInternal(external));
+
+        assertEquals(unchanged, aliaser.getExternal(unchanged));
+        assertEquals(unchanged, aliaser.getInternal(unchanged));
+
+        // Ensure replacement is only the prefix
+        internal += "/nifi-registry/";
+        external += "/nifi-registry/";
+        unchanged += "/nifi-registry/";
+
+        assertEquals(external, aliaser.getExternal(internal));
+        assertEquals(internal, aliaser.getInternal(external));
+
+        assertEquals(unchanged, aliaser.getExternal(unchanged));
+        assertEquals(unchanged, aliaser.getInternal(unchanged));
+    }
+
+    @Test
+    public void testMultipleAliases() {
+        String internal1 = "https://registry-1.com:18443";
+        String external1 = "http://localhost:18080";
+        String internal2 = "https://registry-2.com:18443";
+        String external2 = "http://localhost:18081";
+        String internal3 = "THIS_NIFI_REGISTRY";
+        String external3 = "http://localhost:18082";
+
+        String unchanged = "https://registry-3.com:18443";
+
+        RegistryUrlAliasService aliaser = new RegistryUrlAliasService(Arrays.asList(createAlias(internal1, external1), createAlias(internal2, external2), createAlias(internal3, external3)));
+
+        assertEquals(external1, aliaser.getExternal(internal1));
+        assertEquals(external2, aliaser.getExternal(internal2));
+        assertEquals(external3, aliaser.getExternal(internal3));
+
+        assertEquals(internal1, aliaser.getInternal(external1));
+        assertEquals(internal2, aliaser.getInternal(external2));
+        assertEquals(internal3, aliaser.getInternal(external3));
+
+        assertEquals(unchanged, aliaser.getExternal(unchanged));
+        assertEquals(unchanged, aliaser.getInternal(unchanged));
+
+        // Ensure replacement is only the prefix
+        internal1 += "/nifi-registry/";
+        internal2 += "/nifi-registry/";
+        internal3 += "/nifi-registry/";
+
+        external1 += "/nifi-registry/";
+        external2 += "/nifi-registry/";
+        external3 += "/nifi-registry/";
+
+        unchanged += "/nifi-registry/";
+
+        assertEquals(external1, aliaser.getExternal(internal1));
+        assertEquals(external2, aliaser.getExternal(internal2));
+        assertEquals(external3, aliaser.getExternal(internal3));
+
+        assertEquals(internal1, aliaser.getInternal(external1));
+        assertEquals(internal2, aliaser.getInternal(external2));
+        assertEquals(internal3, aliaser.getInternal(external3));
+
+        assertEquals(unchanged, aliaser.getExternal(unchanged));
+        assertEquals(unchanged, aliaser.getInternal(unchanged));
+    }
+
+    @Test
+    public void testMigrationPath() {
+        String internal1 = "INTERNAL_TOKEN";
+        String internal2 = "http://old.registry.url";
+        String external = "https://new.registry.url";
+
+        RegistryUrlAliasService aliaser = new RegistryUrlAliasService(Arrays.asList(createAlias(internal1, external), createAlias(internal2, external)));
+
+        assertEquals(internal1, aliaser.getInternal(external));
+
+        assertEquals(external, aliaser.getExternal(internal1));
+        assertEquals(external, aliaser.getExternal(internal2));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDuplicateInternalTokens() {
+        String internal = "THIS_NIFI_REGISTRY";
+        String external1 = "http://localhost:18080";
+        String external2 = "http://localhost:18081";
+
+        new RegistryUrlAliasService(Arrays.asList(createAlias(internal, external1), createAlias(internal, external2)));
+    }
+}
diff --git a/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java b/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
index aa4d19e..31133eb 100644
--- a/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
+++ b/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
@@ -61,6 +61,7 @@
     public static final String EXTENSION_DIR_PREFIX = "nifi.registry.extension.dir.";
 
     public static final String PROVIDERS_CONFIGURATION_FILE = "nifi.registry.providers.configuration.file";
+    public static final String REGISTRY_ALIAS_CONFIGURATION_FILE = "nifi.registry.registry.alias.configuration.file";
 
     public static final String EXTENSIONS_WORKING_DIR = "nifi.registry.extensions.working.directory";
 
@@ -89,6 +90,7 @@
     public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
     public static final String DEFAULT_WAR_DIR = "./lib";
     public static final String DEFAULT_PROVIDERS_CONFIGURATION_FILE = "./conf/providers.xml";
+    public static final String DEFAULT_REGISTRY_ALIAS_CONFIGURATION_FILE = "./conf/registry-aliases.xml";
     public static final String DEFAULT_SECURITY_AUTHORIZERS_CONFIGURATION_FILE = "./conf/authorizers.xml";
     public static final String DEFAULT_SECURITY_IDENTITY_PROVIDER_CONFIGURATION_FILE = "./conf/identity-providers.xml";
     public static final String DEFAULT_AUTHENTICATION_EXPIRATION = "12 hours";
@@ -173,6 +175,10 @@
         return getPropertyAsFile(PROVIDERS_CONFIGURATION_FILE, DEFAULT_PROVIDERS_CONFIGURATION_FILE);
     }
 
+    public File getRegistryAliasConfigurationFile() {
+        return getPropertyAsFile(REGISTRY_ALIAS_CONFIGURATION_FILE, DEFAULT_REGISTRY_ALIAS_CONFIGURATION_FILE);
+    }
+
     public String getLegacyDatabaseDirectory() {
         return getProperty(DATABASE_DIRECTORY);
     }
diff --git a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
index 6d8771f..e70c1bb 100644
--- a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
+++ b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
@@ -42,6 +42,9 @@
 # providers properties #
 nifi.registry.providers.configuration.file=${nifi.registry.providers.configuration.file}
 
+# registry alias properties #
+nifi.registry.registry.alias.configuration.file=${nifi.registry.registry.alias.configuration.file}
+
 # extensions working dir #
 nifi.registry.extensions.working.directory=${nifi.registry.extensions.working.directory}
 
diff --git a/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/registry-aliases.xml b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/registry-aliases.xml
new file mode 100644
index 0000000..9bd1b2d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/registry-aliases.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  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.
+-->
+<aliases>
+    <!--
+    <alias>
+        <internal>LOCAL_NIFI_REGISTRY</internal>
+        <external>http://registry.nifi.apache.org:18080</external>
+    </alias>
+    -->
+</aliases>
\ No newline at end of file