You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:39 UTC

[40/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring project structure to better isolate extensions

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/pom.xml b/nifi-registry-core/nifi-registry-framework/pom.xml
new file mode 100644
index 0000000..ca74ab1
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/pom.xml
@@ -0,0 +1,366 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.apache.nifi.registry</groupId>
+        <artifactId>nifi-registry-core</artifactId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>nifi-registry-framework</artifactId>
+    <packaging>jar</packaging>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+            </resource>
+            <resource>
+                <directory>src/main/xsd</directory>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jaxb2-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>providers</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/main/xsd/providers.xsd</source>
+                            </sources>
+                            <packageName>org.apache.nifi.registry.provider.generated</packageName>
+                            <clearOutputDir>false</clearOutputDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>authorizers</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/main/xsd/authorizers.xsd</source>
+                            </sources>
+                            <packageName>org.apache.nifi.registry.security.authorization.generated</packageName>
+                            <clearOutputDir>false</clearOutputDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>authorizations</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/main/xsd/authorizations.xsd</source>
+                            </sources>
+                            <packageName>org.apache.nifi.registry.security.authorization.file.generated</packageName>
+                            <clearOutputDir>false</clearOutputDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>tenants</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/main/xsd/tenants.xsd</source>
+                            </sources>
+                            <packageName>org.apache.nifi.registry.security.authorization.file.tenants.generated</packageName>
+                            <clearOutputDir>false</clearOutputDir>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>identity-providers</id>
+                        <goals>
+                            <goal>xjc</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/main/xsd/identity-providers.xsd</source>
+                            </sources>
+                            <packageName>org.apache.nifi.registry.security.authentication.generated</packageName>
+                            <clearOutputDir>false</clearOutputDir>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <excludes>**/generated/*.java</excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.gmavenplus</groupId>
+                <artifactId>gmavenplus-plugin</artifactId>
+                <version>1.5</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>addTestSources</goal>
+                            <goal>testCompile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.6.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes combine.children="append">
+                        <exclude>src/test/resources/serialization/json/no-version.snapshot</exclude>
+                        <exclude>src/test/resources/serialization/json/non-integer-version.snapshot</exclude>
+                        <exclude>src/test/resources/serialization/ver1.snapshot</exclude>
+                        <exclude>src/test/resources/serialization/ver2.snapshot</exclude>
+                        <exclude>src/test/resources/serialization/ver3.snapshot</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-data-model</artifactId>
+            <version>0.3.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-properties</artifactId>
+            <version>0.3.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-utils</artifactId>
+            <version>0.3.0-SNAPSHOT</version>
+            <scope>provided</scope> <!-- will be in lib dir -->
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-provider-api</artifactId>
+            <scope>provided</scope> <!-- will be in lib dir -->
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-security-api</artifactId>
+            <scope>provided</scope> <!-- will be in lib dir -->
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-security-utils</artifactId>
+            <version>0.3.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+            <version>${spring.boot.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-ldap</artifactId>
+            <version>${spring.security.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.security</groupId>
+                    <artifactId>spring-security-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-beans</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-context</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-tx</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+            <version>1.55</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish</groupId>
+            <artifactId>javax.el</artifactId>
+        </dependency>
+        <!-- We're using spring-boot-starter-data-jpa to bring in spring-data-jdbc and a few other dependencies, but
+            we aren't actually using any JPA dependencies, this also brings in the standard Spring dependencies for the backend -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+            <version>${spring.boot.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.tomcat</groupId>
+                    <artifactId>tomcat-jdbc</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework.data</groupId>
+                    <artifactId>spring-data-jpa</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.hibernate</groupId>
+                    <artifactId>hibernate-entitymanager</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.hibernate</groupId>
+                    <artifactId>hibernate-core</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.flywaydb</groupId>
+            <artifactId>flyway-core</artifactId>
+            <version>${flyway.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <version>1.4.196</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jgit</groupId>
+            <artifactId>org.eclipse.jgit</artifactId>
+            <version>4.11.0.201803080745-r</version>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.54</version>
+        </dependency>
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+            <version>1.20</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.module</groupId>
+            <artifactId>jackson-module-jaxb-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <version>${spring.boot.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.flywaydb.flyway-test-extensions</groupId>
+            <artifactId>flyway-spring-test</artifactId>
+            <version>${flyway.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-context</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-jdbc</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <version>1.0-groovy-2.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-all</artifactId>
+            <version>2.4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib-nodep</artifactId>
+            <version>2.2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-all</artifactId>
+            <version>2.0.0-M24</version>
+            <scope>test</scope>
+	</dependency>
+	<dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-flow-diff</artifactId>
+            <version>0.3.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayMigrationStrategy.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayMigrationStrategy.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayMigrationStrategy.java
new file mode 100644
index 0000000..7748acf
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/CustomFlywayMigrationStrategy.java
@@ -0,0 +1,157 @@
+/*
+ * 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.db;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.db.migration.BucketEntityV1;
+import org.apache.nifi.registry.db.migration.FlowEntityV1;
+import org.apache.nifi.registry.db.migration.FlowSnapshotEntityV1;
+import org.apache.nifi.registry.db.migration.LegacyDataSourceFactory;
+import org.apache.nifi.registry.db.migration.LegacyDatabaseService;
+import org.apache.nifi.registry.db.migration.LegacyEntityMapper;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.service.MetadataService;
+import org.flywaydb.core.Flyway;
+import org.flywaydb.core.api.FlywayException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.io.File;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Custom Flyway migration strategy that lets us perform data migration from the original database used in the
+ * 0.1.0 release, to the new database. The data migration will be triggered when it is determined that new database
+ * is brand new AND the legacy DB properties are specified. If the primary database already contains the 'BUCKET' table,
+ * or if the legacy database properties are not specified, then no data migration is performed.
+ */
+@Component
+public class CustomFlywayMigrationStrategy implements FlywayMigrationStrategy {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CustomFlywayMigrationStrategy.class);
+
+    private NiFiRegistryProperties properties;
+
+    @Autowired
+    public CustomFlywayMigrationStrategy(final NiFiRegistryProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public void migrate(Flyway flyway) {
+        final boolean newDatabase = isNewDatabase(flyway.getDataSource());
+        if (newDatabase) {
+            LOGGER.info("First time initializing database...");
+        } else {
+            LOGGER.info("Found existing database...");
+        }
+
+        boolean existingLegacyDatabase = false;
+        if (!StringUtils.isBlank(properties.getLegacyDatabaseDirectory())) {
+            LOGGER.info("Found legacy database properties...");
+
+            final File legacyDatabaseFile = new File(properties.getLegacyDatabaseDirectory(), "nifi-registry.mv.db");
+            if (legacyDatabaseFile.exists()) {
+                LOGGER.info("Found legacy database file...");
+                existingLegacyDatabase = true;
+            } else {
+                LOGGER.info("Did not find legacy database file...");
+                existingLegacyDatabase = false;
+            }
+        }
+
+        // If newDatabase is true, then we need to run the Flyway migration first to create all the tables, then the data migration
+        // If newDatabase is false, then we need to run the Flyway migration to run any schema updates, but no data migration
+
+        flyway.migrate();
+
+        if (newDatabase && existingLegacyDatabase) {
+            final LegacyDataSourceFactory legacyDataSourceFactory = new LegacyDataSourceFactory(properties);
+            final DataSource legacyDataSource = legacyDataSourceFactory.getDataSource();
+            final DataSource primaryDataSource = flyway.getDataSource();
+            migrateData(legacyDataSource, primaryDataSource);
+        }
+    }
+
+    /**
+     * Determines if the database represented by this data source is being initialized for the first time based on
+     * whether or not the table named 'BUCKET' or 'bucket' already exists.
+     *
+     * @param dataSource the data source
+     * @return true if the database has never been initialized before, false otherwise
+     */
+    private boolean isNewDatabase(final DataSource dataSource) {
+        try (final Connection connection = dataSource.getConnection();
+             final ResultSet rsUpper = connection.getMetaData().getTables(null, null, "BUCKET", null);
+             final ResultSet rsLower = connection.getMetaData().getTables(null, null, "bucket", null)) {
+            return !rsUpper.next() && !rsLower.next();
+        } catch (SQLException e) {
+            LOGGER.error(e.getMessage(), e);
+            throw new FlywayException("Unable to obtain connection from Flyway DataSource", e);
+        }
+    }
+
+    /**
+     * Transfers all data from the source to the destination.
+     *
+     * @param source the legacy H2 DataSource
+     * @param dest the new destination DataSource
+     */
+    private void migrateData(final DataSource source, final DataSource dest) {
+        final LegacyDatabaseService legacyDatabaseService = new LegacyDatabaseService(source);
+
+        final JdbcTemplate destJdbcTemplate = new JdbcTemplate(dest);
+        final MetadataService destMetadataService = new DatabaseMetadataService(destJdbcTemplate);
+
+        LOGGER.info("Migrating data from legacy database to new new database...");
+
+        // Migrate buckets
+        final List<BucketEntityV1> sourceBuckets = legacyDatabaseService.getAllBuckets();
+        LOGGER.info("Migrating {} buckets..", new Object[]{sourceBuckets.size()});
+
+        sourceBuckets.stream()
+                .map(b -> LegacyEntityMapper.createBucketEntity(b))
+                .forEach(b -> destMetadataService.createBucket(b));
+
+        // Migrate flows
+        final List<FlowEntityV1> sourceFlows = legacyDatabaseService.getAllFlows();
+        LOGGER.info("Migrating {} flows..", new Object[]{sourceFlows.size()});
+
+        sourceFlows.stream()
+                .map(f -> LegacyEntityMapper.createFlowEntity(f))
+                .forEach(f -> destMetadataService.createFlow(f));
+
+        // Migrate flow snapshots
+        final List<FlowSnapshotEntityV1> sourceSnapshots = legacyDatabaseService.getAllFlowSnapshots();
+        LOGGER.info("Migrating {} flow snapshots..", new Object[]{sourceSnapshots.size()});
+
+        sourceSnapshots.stream()
+                .map(fs -> LegacyEntityMapper.createFlowSnapshotEntity(fs))
+                .forEach(fs -> destMetadataService.createFlowSnapshot(fs));
+
+        LOGGER.info("Data migration complete!");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DataSourceFactory.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DataSourceFactory.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DataSourceFactory.java
new file mode 100644
index 0000000..29c132e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DataSourceFactory.java
@@ -0,0 +1,97 @@
+/*
+ * 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.db;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.sql.DataSource;
+
+/**
+ * Overriding Spring Boot's normal automatic creation of a DataSource in order to use the properties
+ * from NiFiRegistryProperties rather than the standard application.properties/yaml.
+ */
+@Configuration
+public class DataSourceFactory {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceFactory.class);
+
+    private final NiFiRegistryProperties properties;
+
+    private DataSource dataSource;
+
+    @Autowired
+    public DataSourceFactory(final NiFiRegistryProperties properties) {
+        this.properties = properties;
+    }
+
+    @Bean
+    @Primary
+    public DataSource getDataSource() {
+        if (dataSource == null) {
+            dataSource = createDataSource();
+        }
+
+        return dataSource;
+    }
+
+    private DataSource createDataSource() {
+        final String databaseUrl = properties.getDatabaseUrl();
+        if (StringUtils.isBlank(databaseUrl)) {
+            throw new IllegalStateException(NiFiRegistryProperties.DATABASE_URL + " is required");
+        }
+
+        final String databaseDriver = properties.getDatabaseDriverClassName();
+        if (StringUtils.isBlank(databaseDriver)) {
+            throw new IllegalStateException(NiFiRegistryProperties.DATABASE_DRIVER_CLASS_NAME + " is required");
+        }
+
+        final String databaseUsername = properties.getDatabaseUsername();
+        if (StringUtils.isBlank(databaseUsername)) {
+            throw new IllegalStateException(NiFiRegistryProperties.DATABASE_USERNAME + " is required");
+        }
+
+        String databasePassword = properties.getDatabasePassword();
+        if (StringUtils.isBlank(databasePassword)) {
+            throw new IllegalStateException(NiFiRegistryProperties.DATABASE_PASSWORD + " is required");
+        }
+
+        final DataSource dataSource = DataSourceBuilder
+                .create()
+                .url(databaseUrl)
+                .driverClassName(databaseDriver)
+                .username(databaseUsername)
+                .password(databasePassword)
+                .build();
+
+        if (dataSource instanceof HikariDataSource) {
+            LOGGER.info("Setting maximum pool size on HikariDataSource to {}", new Object[]{properties.getDatabaseMaxConnections()});
+            ((HikariDataSource)dataSource).setMaximumPoolSize(properties.getDatabaseMaxConnections());
+        }
+
+        return dataSource;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java
new file mode 100644
index 0000000..1d4f721
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseKeyService.java
@@ -0,0 +1,136 @@
+/*
+ * 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.db;
+
+import org.apache.nifi.registry.db.entity.KeyEntity;
+import org.apache.nifi.registry.db.mapper.KeyEntityRowMapper;
+import org.apache.nifi.registry.security.key.Key;
+import org.apache.nifi.registry.security.key.KeyService;
+import org.apache.nifi.registry.service.DataModelMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+@Service
+public class DatabaseKeyService implements KeyService {
+
+    private static final Logger logger = LoggerFactory.getLogger(DatabaseKeyService.class);
+
+    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+    private final Lock readLock = lock.readLock();
+    private final Lock writeLock = lock.writeLock();
+
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public DatabaseKeyService(final JdbcTemplate jdbcTemplate) {
+        this.jdbcTemplate = jdbcTemplate;
+    }
+
+    @Override
+    public Key getKey(String id) {
+        if (id == null) {
+            throw new IllegalArgumentException("Id cannot be null");
+        }
+
+        Key key = null;
+        readLock.lock();
+        try {
+            final String sql = "SELECT * FROM signing_key WHERE id = ?";
+
+            KeyEntity keyEntity;
+            try {
+                keyEntity = jdbcTemplate.queryForObject(sql, new KeyEntityRowMapper(), id);
+            } catch (EmptyResultDataAccessException e) {
+                keyEntity = null;
+            }
+
+            if (keyEntity != null) {
+                key = DataModelMapper.map(keyEntity);
+            } else {
+                logger.debug("No signing key found with id='" + id + "'");
+            }
+        } finally {
+            readLock.unlock();
+        }
+        return key;
+    }
+
+    @Override
+    public Key getOrCreateKey(String tenantIdentity) {
+        if (tenantIdentity == null) {
+            throw new IllegalArgumentException("Identity cannot be null");
+        }
+
+        Key key;
+        writeLock.lock();
+        try {
+            final String selectSql = "SELECT * FROM signing_key WHERE tenant_identity = ?";
+
+            KeyEntity existingKeyEntity;
+            try {
+                existingKeyEntity = jdbcTemplate.queryForObject(selectSql, new KeyEntityRowMapper(), tenantIdentity);
+            } catch (EmptyResultDataAccessException e) {
+                existingKeyEntity = null;
+            }
+
+            if (existingKeyEntity == null) {
+                logger.debug("No key found with identity='" + tenantIdentity + "'. Creating new key.");
+
+                final KeyEntity newKeyEntity = new KeyEntity();
+                newKeyEntity.setId(UUID.randomUUID().toString());
+                newKeyEntity.setTenantIdentity(tenantIdentity);
+                newKeyEntity.setKeyValue(UUID.randomUUID().toString());
+
+                final String insertSql = "INSERT INTO signing_key (ID, TENANT_IDENTITY, KEY_VALUE) VALUES (?, ?, ?)";
+                jdbcTemplate.update(insertSql, newKeyEntity.getId(), newKeyEntity.getTenantIdentity(), newKeyEntity.getKeyValue());
+
+                key = DataModelMapper.map(newKeyEntity);
+            } else {
+                key = DataModelMapper.map(existingKeyEntity);
+            }
+        } finally {
+            writeLock.unlock();
+        }
+        return key;
+    }
+
+    @Override
+    public void deleteKey(String tenantIdentity) {
+        if (tenantIdentity == null) {
+            throw new IllegalArgumentException("Identity cannot be null");
+        }
+
+        writeLock.lock();
+        try {
+            logger.debug("Deleting key with identity='" + tenantIdentity + "'.");
+            final String deleteSql = "DELETE FROM signing_key WHERE tenant_identity = ?";
+            jdbcTemplate.update(deleteSql, tenantIdentity);
+        } finally {
+            writeLock.unlock();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
new file mode 100644
index 0000000..4d32790
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/DatabaseMetadataService.java
@@ -0,0 +1,441 @@
+/*
+ * 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.db;
+
+import org.apache.nifi.registry.db.entity.BucketEntity;
+import org.apache.nifi.registry.db.entity.BucketItemEntity;
+import org.apache.nifi.registry.db.entity.BucketItemEntityType;
+import org.apache.nifi.registry.db.entity.FlowEntity;
+import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
+import org.apache.nifi.registry.db.mapper.BucketEntityRowMapper;
+import org.apache.nifi.registry.db.mapper.BucketItemEntityRowMapper;
+import org.apache.nifi.registry.db.mapper.FlowEntityRowMapper;
+import org.apache.nifi.registry.db.mapper.FlowSnapshotEntityRowMapper;
+import org.apache.nifi.registry.service.MetadataService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Repository
+public class DatabaseMetadataService implements MetadataService {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public DatabaseMetadataService(final JdbcTemplate jdbcTemplate) {
+        this.jdbcTemplate = jdbcTemplate;
+    }
+
+    //----------------- Buckets ---------------------------------
+
+    @Override
+    public BucketEntity createBucket(final BucketEntity b) {
+        final String sql = "INSERT INTO bucket (ID, NAME, DESCRIPTION, CREATED) VALUES (?, ?, ?, ?)";
+        jdbcTemplate.update(sql, b.getId(), b.getName(), b.getDescription(), b.getCreated());
+        return b;
+    }
+
+    @Override
+    public BucketEntity getBucketById(final String bucketIdentifier) {
+        final String sql = "SELECT * FROM bucket WHERE id = ?";
+        try {
+            return jdbcTemplate.queryForObject(sql, new BucketEntityRowMapper(), bucketIdentifier);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public List<BucketEntity> getBucketsByName(final String name) {
+        final String sql = "SELECT * FROM bucket WHERE name = ? ORDER BY name ASC";
+        return jdbcTemplate.query(sql, new Object[] {name} , new BucketEntityRowMapper());
+    }
+
+    @Override
+    public BucketEntity updateBucket(final BucketEntity bucket) {
+        final String sql = "UPDATE bucket SET name = ?, description = ? WHERE id = ?";
+        jdbcTemplate.update(sql, bucket.getName(), bucket.getDescription(), bucket.getId());
+        return bucket;
+    }
+
+    @Override
+    public void deleteBucket(final BucketEntity bucket) {
+        final String snapshotDeleteSql = "DELETE FROM flow_snapshot WHERE flow_id IN ( " +
+                    "SELECT f.id FROM flow f, bucket_item item WHERE f.id = item.id AND item.bucket_id = ?" +
+                ")";
+        jdbcTemplate.update(snapshotDeleteSql, bucket.getId());
+
+        final String flowDeleteSql = "DELETE FROM flow WHERE id IN ( " +
+                    "SELECT f.id FROM flow f, bucket_item item WHERE f.id = item.id AND item.bucket_id = ?" +
+                ")";
+        jdbcTemplate.update(flowDeleteSql, bucket.getId());
+
+        final String itemDeleteSql = "DELETE FROM bucket_item WHERE bucket_id = ?";
+        jdbcTemplate.update(itemDeleteSql, bucket.getId());
+
+        final String sql = "DELETE FROM bucket WHERE id = ?";
+        jdbcTemplate.update(sql, bucket.getId());
+    }
+
+    @Override
+    public List<BucketEntity> getBuckets(final Set<String> bucketIds) {
+        if (bucketIds == null || bucketIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM bucket WHERE id IN (");
+        for (int i=0; i < bucketIds.size(); i++) {
+            if (i > 0) {
+                sqlBuilder.append(", ");
+            }
+            sqlBuilder.append("?");
+        }
+        sqlBuilder.append(") ");
+        sqlBuilder.append("ORDER BY name ASC");
+
+        return jdbcTemplate.query(sqlBuilder.toString(), bucketIds.toArray(), new BucketEntityRowMapper());
+    }
+
+    @Override
+    public List<BucketEntity> getAllBuckets() {
+        final String sql = "SELECT * FROM bucket ORDER BY name ASC";
+        return jdbcTemplate.query(sql, new BucketEntityRowMapper());
+    }
+
+    //----------------- BucketItems ---------------------------------
+
+    @Override
+    public List<BucketItemEntity> getBucketItems(final String bucketIdentifier) {
+        final String sql =
+                "SELECT " +
+                    "item.id as ID, " +
+                    "item.name as NAME, " +
+                    "item.description as DESCRIPTION, " +
+                    "item.created as CREATED, " +
+                    "item.modified as MODIFIED, " +
+                    "item.item_type as ITEM_TYPE, " +
+                    "b.id as BUCKET_ID, " +
+                    "b.name as BUCKET_NAME " +
+                "FROM " +
+                        "bucket_item item, bucket b " +
+                "WHERE " +
+                        "item.bucket_id = b.id " +
+                "AND " +
+                        "item.bucket_id = ?";
+
+        final List<BucketItemEntity> items = jdbcTemplate.query(sql, new Object[] { bucketIdentifier }, new BucketItemEntityRowMapper());
+        return getItemsWithCounts(items);
+    }
+
+    @Override
+    public List<BucketItemEntity> getBucketItems(final Set<String> bucketIds) {
+        if (bucketIds == null || bucketIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final StringBuilder sqlBuilder = new StringBuilder(
+                "SELECT " +
+                        "item.id as ID, " +
+                        "item.name as NAME, " +
+                        "item.description as DESCRIPTION, " +
+                        "item.created as CREATED, " +
+                        "item.modified as MODIFIED, " +
+                        "item.item_type as ITEM_TYPE, " +
+                        "b.id as BUCKET_ID, " +
+                        "b.name as BUCKET_NAME " +
+                "FROM " +
+                        "bucket_item item, bucket b " +
+                "WHERE " +
+                        "item.bucket_id = b.id " +
+                "AND " +
+                        "item.bucket_id IN (");
+
+        for (int i=0; i < bucketIds.size(); i++) {
+            if (i > 0) {
+                sqlBuilder.append(", ");
+            }
+            sqlBuilder.append("?");
+        }
+        sqlBuilder.append(")");
+
+        final List<BucketItemEntity> items = jdbcTemplate.query(sqlBuilder.toString(), bucketIds.toArray(), new BucketItemEntityRowMapper());
+        return getItemsWithCounts(items);
+    }
+
+    private List<BucketItemEntity> getItemsWithCounts(final Iterable<BucketItemEntity> items) {
+        final Map<String,Long> snapshotCounts = getFlowSnapshotCounts();
+
+        final List<BucketItemEntity> itemWithCounts = new ArrayList<>();
+        for (final BucketItemEntity item : items) {
+            if (item.getType() == BucketItemEntityType.FLOW) {
+                final Long snapshotCount = snapshotCounts.get(item.getId());
+                if (snapshotCount != null) {
+                    final FlowEntity flowEntity = (FlowEntity) item;
+                    flowEntity.setSnapshotCount(snapshotCount);
+                }
+            }
+
+            itemWithCounts.add(item);
+        }
+
+        return itemWithCounts;
+    }
+
+    private Map<String,Long> getFlowSnapshotCounts() {
+        final String sql = "SELECT flow_id, count(*) FROM flow_snapshot GROUP BY flow_id";
+
+        final Map<String,Long> results = new HashMap<>();
+        jdbcTemplate.query(sql, (rs) -> {
+            results.put(rs.getString(1), rs.getLong(2));
+        });
+        return results;
+    }
+
+    private Long getFlowSnapshotCount(final String flowIdentifier) {
+        final String sql = "SELECT count(*) FROM flow_snapshot WHERE flow_id = ?";
+
+        return jdbcTemplate.queryForObject(sql, new Object[] {flowIdentifier}, (rs, num) -> {
+            return rs.getLong(1);
+        });
+    }
+
+    //----------------- Flows ---------------------------------
+
+    @Override
+    public FlowEntity createFlow(final FlowEntity flow) {
+        final String itemSql = "INSERT INTO bucket_item (ID, NAME, DESCRIPTION, CREATED, MODIFIED, ITEM_TYPE, BUCKET_ID) VALUES (?, ?, ?, ?, ?, ?, ?)";
+
+        jdbcTemplate.update(itemSql,
+                flow.getId(),
+                flow.getName(),
+                flow.getDescription(),
+                flow.getCreated(),
+                flow.getModified(),
+                flow.getType().toString(),
+                flow.getBucketId());
+
+        final String flowSql = "INSERT INTO flow (ID) VALUES (?)";
+
+        jdbcTemplate.update(flowSql, flow.getId());
+
+        return flow;
+    }
+
+    @Override
+    public FlowEntity getFlowById(final String flowIdentifier) {
+        final String sql = "SELECT * FROM flow f, bucket_item item WHERE f.id = ? AND item.id = f.id";
+        try {
+            return jdbcTemplate.queryForObject(sql, new FlowEntityRowMapper(), flowIdentifier);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public FlowEntity getFlowByIdWithSnapshotCounts(final String flowIdentifier) {
+        final FlowEntity flowEntity = getFlowById(flowIdentifier);
+        if (flowEntity == null) {
+            return flowEntity;
+        }
+
+        final Long snapshotCount = getFlowSnapshotCount(flowIdentifier);
+        if (snapshotCount != null) {
+            flowEntity.setSnapshotCount(snapshotCount);
+        }
+
+        return flowEntity;
+    }
+
+    @Override
+    public List<FlowEntity> getFlowsByName(final String name) {
+        final String sql = "SELECT * FROM flow f, bucket_item item WHERE item.name = ? AND item.id = f.id";
+        return jdbcTemplate.query(sql, new Object[] {name}, new FlowEntityRowMapper());
+    }
+
+    @Override
+    public List<FlowEntity> getFlowsByName(final String bucketIdentifier, final String name) {
+        final String sql = "SELECT * FROM flow f, bucket_item item WHERE item.name = ? AND item.id = f.id AND item.bucket_id = ?";
+        return jdbcTemplate.query(sql, new Object[] {name, bucketIdentifier}, new FlowEntityRowMapper());
+    }
+
+    @Override
+    public List<FlowEntity> getFlowsByBucket(final String bucketIdentifier) {
+        final String sql = "SELECT * FROM flow f, bucket_item item WHERE item.bucket_id = ? AND item.id = f.id";
+        final List<FlowEntity> flows = jdbcTemplate.query(sql, new Object[] {bucketIdentifier}, new FlowEntityRowMapper());
+
+        final Map<String,Long> snapshotCounts = getFlowSnapshotCounts();
+        for (final FlowEntity flowEntity : flows) {
+            final Long snapshotCount = snapshotCounts.get(flowEntity.getId());
+            if (snapshotCount != null) {
+                flowEntity.setSnapshotCount(snapshotCount);
+            }
+        }
+
+        return flows;
+    }
+
+    @Override
+    public FlowEntity updateFlow(final FlowEntity flow) {
+        flow.setModified(new Date());
+
+        final String sql = "UPDATE bucket_item SET name = ?, description = ?, modified = ? WHERE id = ?";
+        jdbcTemplate.update(sql, flow.getName(), flow.getDescription(), flow.getModified(), flow.getId());
+        return flow;
+    }
+
+    @Override
+    public void deleteFlow(final FlowEntity flow) {
+        final String snapshotDeleteSql = "DELETE FROM flow_snapshot WHERE flow_id = ?";
+        jdbcTemplate.update(snapshotDeleteSql, flow.getId());
+
+        final String flowDeleteSql = "DELETE FROM flow WHERE id = ?";
+        jdbcTemplate.update(flowDeleteSql, flow.getId());
+
+        final String itemDeleteSql = "DELETE FROM bucket_item WHERE id = ?";
+        jdbcTemplate.update(itemDeleteSql, flow.getId());
+    }
+
+    //----------------- Flow Snapshots ---------------------------------
+
+    @Override
+    public FlowSnapshotEntity createFlowSnapshot(final FlowSnapshotEntity flowSnapshot) {
+        final String sql = "INSERT INTO flow_snapshot (FLOW_ID, VERSION, CREATED, CREATED_BY, COMMENTS) VALUES (?, ?, ?, ?, ?)";
+
+        jdbcTemplate.update(sql,
+                flowSnapshot.getFlowId(),
+                flowSnapshot.getVersion(),
+                flowSnapshot.getCreated(),
+                flowSnapshot.getCreatedBy(),
+                flowSnapshot.getComments());
+
+        return flowSnapshot;
+    }
+
+    @Override
+    public FlowSnapshotEntity getFlowSnapshot(final String flowIdentifier, final Integer version) {
+        final String sql =
+                "SELECT " +
+                        "fs.flow_id, " +
+                        "fs.version, " +
+                        "fs.created, " +
+                        "fs.created_by, " +
+                        "fs.comments " +
+                "FROM " +
+                        "flow_snapshot fs, " +
+                        "flow f, " +
+                        "bucket_item item " +
+                "WHERE " +
+                        "item.id = f.id AND " +
+                        "f.id = ? AND " +
+                        "f.id = fs.flow_id AND " +
+                        "fs.version = ?";
+
+        try {
+            return jdbcTemplate.queryForObject(sql, new FlowSnapshotEntityRowMapper(),
+                    flowIdentifier, version);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public FlowSnapshotEntity getLatestSnapshot(final String flowIdentifier) {
+        final String sql = "SELECT * FROM flow_snapshot WHERE flow_id = ? ORDER BY version DESC LIMIT 1";
+
+        try {
+            return jdbcTemplate.queryForObject(sql, new FlowSnapshotEntityRowMapper(), flowIdentifier);
+        } catch (EmptyResultDataAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public List<FlowSnapshotEntity> getSnapshots(final String flowIdentifier) {
+        final String sql =
+                "SELECT " +
+                        "fs.flow_id, " +
+                        "fs.version, " +
+                        "fs.created, " +
+                        "fs.created_by, " +
+                        "fs.comments " +
+                "FROM " +
+                        "flow_snapshot fs, " +
+                        "flow f, " +
+                        "bucket_item item " +
+                "WHERE " +
+                        "item.id = f.id AND " +
+                        "f.id = ? AND " +
+                        "f.id = fs.flow_id";
+
+        final Object[] args = new Object[] { flowIdentifier };
+        return jdbcTemplate.query(sql, args, new FlowSnapshotEntityRowMapper());
+    }
+
+    @Override
+    public void deleteFlowSnapshot(final FlowSnapshotEntity flowSnapshot) {
+        final String sql = "DELETE FROM flow_snapshot WHERE flow_id = ? AND version = ?";
+        jdbcTemplate.update(sql, flowSnapshot.getFlowId(), flowSnapshot.getVersion());
+    }
+
+    //----------------- BucketItems ---------------------------------
+
+    @Override
+    public Set<String> getBucketFields() {
+        final Set<String> fields = new LinkedHashSet<>();
+        fields.add("ID");
+        fields.add("NAME");
+        fields.add("DESCRIPTION");
+        fields.add("CREATED");
+        return fields;
+    }
+
+    @Override
+    public Set<String> getBucketItemFields() {
+        final Set<String> fields = new LinkedHashSet<>();
+        fields.add("ID");
+        fields.add("NAME");
+        fields.add("DESCRIPTION");
+        fields.add("CREATED");
+        fields.add("MODIFIED");
+        fields.add("ITEM_TYPE");
+        fields.add("BUCKET_ID");
+        return fields;
+    }
+
+    @Override
+    public Set<String> getFlowFields() {
+        final Set<String> fields = new LinkedHashSet<>();
+        fields.add("ID");
+        fields.add("NAME");
+        fields.add("DESCRIPTION");
+        fields.add("CREATED");
+        fields.add("MODIFIED");
+        fields.add("ITEM_TYPE");
+        fields.add("BUCKET_ID");
+        return fields;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java
new file mode 100644
index 0000000..71d5a92
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketEntity.java
@@ -0,0 +1,83 @@
+/*
+ * 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.db.entity;
+
+import java.util.Date;
+import java.util.Objects;
+
+public class BucketEntity {
+
+    private String id;
+
+    private String name;
+
+    private String description;
+
+    private Date created;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(this.id);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        final BucketEntity other = (BucketEntity) obj;
+        return Objects.equals(this.id, other.id);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntity.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntity.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntity.java
new file mode 100644
index 0000000..cdfa963
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntity.java
@@ -0,0 +1,123 @@
+/*
+ * 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.db.entity;
+
+import java.util.Date;
+import java.util.Objects;
+
+public class BucketItemEntity {
+
+    private String id;
+
+    private String name;
+
+    private String description;
+
+    private Date created;
+
+    private Date modified;
+
+    // NOTE: sub-classes should ensure that the type is set appropriately by overriding the getter/setter
+    private BucketItemEntityType type;
+
+    private String bucketId;
+
+    private String bucketName;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public Date getModified() {
+        return modified;
+    }
+
+    public void setModified(Date modified) {
+        this.modified = modified;
+    }
+
+    public BucketItemEntityType getType() {
+        return type;
+    }
+
+    public void setType(BucketItemEntityType type) {
+        this.type = type;
+    }
+
+    public String getBucketId() {
+        return bucketId;
+    }
+
+    public void setBucketId(String bucketId) {
+        this.bucketId = bucketId;
+    }
+
+    public String getBucketName() {
+        return bucketName;
+    }
+
+    public void setBucketName(String bucketName) {
+        this.bucketName = bucketName;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(this.id);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        final BucketItemEntity other = (BucketItemEntity) obj;
+        return Objects.equals(this.id, other.id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntityType.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntityType.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntityType.java
new file mode 100644
index 0000000..e78b2b1
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/BucketItemEntityType.java
@@ -0,0 +1,42 @@
+/*
+ * 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.db.entity;
+
+/**
+ * Possible types of BucketItemEntity.
+ */
+public enum BucketItemEntityType {
+
+    FLOW(Values.FLOW);
+
+    private final String value;
+
+    BucketItemEntityType(final String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+
+    // need these constants to reference from @DiscriminatorValue
+    public static class Values {
+        public static final String FLOW = "FLOW";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowEntity.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowEntity.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowEntity.java
new file mode 100644
index 0000000..b978168
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowEntity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.db.entity;
+
+public class FlowEntity extends BucketItemEntity {
+
+    private long snapshotCount;
+
+    public FlowEntity() {
+        setType(BucketItemEntityType.FLOW);
+    }
+
+    public long getSnapshotCount() {
+        return snapshotCount;
+    }
+
+    public void setSnapshotCount(long snapshotCount) {
+        this.snapshotCount = snapshotCount;
+    }
+
+    @Override
+    public void setType(BucketItemEntityType type) {
+        if (BucketItemEntityType.FLOW != type) {
+            throw new IllegalStateException("Must set type to FLOW");
+        }
+        super.setType(type);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowSnapshotEntity.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowSnapshotEntity.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowSnapshotEntity.java
new file mode 100644
index 0000000..3143a6e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/FlowSnapshotEntity.java
@@ -0,0 +1,93 @@
+/*
+ * 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.db.entity;
+
+import java.util.Date;
+import java.util.Objects;
+
+public class FlowSnapshotEntity {
+
+    private String flowId;
+
+    private Integer version;
+
+    private Date created;
+
+    private String createdBy;
+
+    private String comments;
+
+    public String getFlowId() {
+        return flowId;
+    }
+
+    public void setFlowId(String flowId) {
+        this.flowId = flowId;
+    }
+
+    public Integer getVersion() {
+        return version;
+    }
+
+    public void setVersion(Integer version) {
+        this.version = version;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public String getComments() {
+        return comments;
+    }
+
+    public void setComments(String comments) {
+        this.comments = comments;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.flowId, this.version);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        if (!(obj instanceof FlowSnapshotEntity)) {
+            return false;
+        }
+
+        final FlowSnapshotEntity other = (FlowSnapshotEntity) obj;
+        return Objects.equals(this.flowId, other.flowId) && Objects.equals(this.version, other.version);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/KeyEntity.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/KeyEntity.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/KeyEntity.java
new file mode 100644
index 0000000..494867f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/KeyEntity.java
@@ -0,0 +1,51 @@
+/*
+ * 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.db.entity;
+
+public class KeyEntity {
+
+    private String id;
+
+    private String tenantIdentity;
+
+    private String keyValue;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getTenantIdentity() {
+        return tenantIdentity;
+    }
+
+    public void setTenantIdentity(String tenantIdentity) {
+        this.tenantIdentity = tenantIdentity;
+    }
+
+    public String getKeyValue() {
+        return keyValue;
+    }
+
+    public void setKeyValue(String keyValue) {
+        this.keyValue = keyValue;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java
new file mode 100644
index 0000000..6c5bc2e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketEntityRowMapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.db.mapper;
+
+import org.apache.nifi.registry.db.entity.BucketEntity;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.lang.Nullable;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class BucketEntityRowMapper implements RowMapper<BucketEntity> {
+
+    @Nullable
+    @Override
+    public BucketEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+        final BucketEntity b = new BucketEntity();
+        b.setId(rs.getString("ID"));
+        b.setName(rs.getString("NAME"));
+        b.setDescription(rs.getString("DESCRIPTION"));
+        b.setCreated(rs.getTimestamp("CREATED"));
+        return b;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketItemEntityRowMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketItemEntityRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketItemEntityRowMapper.java
new file mode 100644
index 0000000..7b3df05
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/BucketItemEntityRowMapper.java
@@ -0,0 +1,58 @@
+/*
+ * 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.db.mapper;
+
+import org.apache.nifi.registry.db.entity.BucketItemEntity;
+import org.apache.nifi.registry.db.entity.BucketItemEntityType;
+import org.apache.nifi.registry.db.entity.FlowEntity;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.lang.Nullable;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class BucketItemEntityRowMapper implements RowMapper<BucketItemEntity> {
+
+    @Nullable
+    @Override
+    public BucketItemEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+        final BucketItemEntityType type = BucketItemEntityType.valueOf(rs.getString("ITEM_TYPE"));
+
+        // Create the appropriate type of sub-class, eventually populate specific data for each type
+        final BucketItemEntity item;
+        switch (type) {
+            case FLOW:
+                item = new FlowEntity();
+                break;
+            default:
+                // should never happen
+                item = new BucketItemEntity();
+                break;
+        }
+
+        // populate fields common to all bucket items
+        item.setId(rs.getString("ID"));
+        item.setName(rs.getString("NAME"));
+        item.setDescription(rs.getString("DESCRIPTION"));
+        item.setCreated(rs.getTimestamp("CREATED"));
+        item.setModified(rs.getTimestamp("MODIFIED"));
+        item.setBucketId(rs.getString("BUCKET_ID"));
+        item.setBucketName(rs.getString("BUCKET_NAME"));
+        item.setType(type);
+        return item;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowEntityRowMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowEntityRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowEntityRowMapper.java
new file mode 100644
index 0000000..acaf343
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowEntityRowMapper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.db.mapper;
+
+import org.apache.nifi.registry.db.entity.BucketItemEntityType;
+import org.apache.nifi.registry.db.entity.FlowEntity;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.lang.Nullable;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class FlowEntityRowMapper implements RowMapper<FlowEntity> {
+
+    @Nullable
+    @Override
+    public FlowEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+        final FlowEntity flowEntity = new FlowEntity();
+        flowEntity.setId(rs.getString("ID"));
+        flowEntity.setName(rs.getString("NAME"));
+        flowEntity.setDescription(rs.getString("DESCRIPTION"));
+        flowEntity.setCreated(rs.getTimestamp("CREATED"));
+        flowEntity.setModified(rs.getTimestamp("MODIFIED"));
+        flowEntity.setBucketId(rs.getString("BUCKET_ID"));
+        flowEntity.setType(BucketItemEntityType.FLOW);
+        return flowEntity;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowSnapshotEntityRowMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowSnapshotEntityRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowSnapshotEntityRowMapper.java
new file mode 100644
index 0000000..07a59b3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/FlowSnapshotEntityRowMapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.db.mapper;
+
+import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.lang.Nullable;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class FlowSnapshotEntityRowMapper implements RowMapper<FlowSnapshotEntity> {
+
+    @Nullable
+    @Override
+    public FlowSnapshotEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+        final FlowSnapshotEntity entity = new FlowSnapshotEntity();
+        entity.setFlowId(rs.getString("FLOW_ID"));
+        entity.setVersion(rs.getInt("VERSION"));
+        entity.setCreated(rs.getTimestamp("CREATED"));
+        entity.setCreatedBy(rs.getString("CREATED_BY"));
+        entity.setComments(rs.getString("COMMENTS"));
+        return entity;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/KeyEntityRowMapper.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/KeyEntityRowMapper.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/KeyEntityRowMapper.java
new file mode 100644
index 0000000..6e190a5
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/KeyEntityRowMapper.java
@@ -0,0 +1,37 @@
+/*
+ * 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.db.mapper;
+
+import org.apache.nifi.registry.db.entity.KeyEntity;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.lang.Nullable;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class KeyEntityRowMapper implements RowMapper<KeyEntity> {
+
+    @Nullable
+    @Override
+    public KeyEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+        final KeyEntity keyEntity = new KeyEntity();
+        keyEntity.setId(rs.getString("ID"));
+        keyEntity.setTenantIdentity(rs.getString("TENANT_IDENTITY"));
+        keyEntity.setKeyValue(rs.getString("KEY_VALUE"));
+        return keyEntity;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/BucketEntityV1.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/BucketEntityV1.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/BucketEntityV1.java
new file mode 100644
index 0000000..94000a5
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/BucketEntityV1.java
@@ -0,0 +1,86 @@
+/*
+ * 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.db.migration;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Bucket DB entity from the original database schema in 0.1.0, used for migration purposes.
+ */
+public class BucketEntityV1 {
+
+    private String id;
+
+    private String name;
+
+    private String description;
+
+    private Date created;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(this.id);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        final BucketEntityV1 other = (BucketEntityV1) obj;
+        return Objects.equals(this.id, other.id);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/FlowEntityV1.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/FlowEntityV1.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/FlowEntityV1.java
new file mode 100644
index 0000000..961c3bd
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/migration/FlowEntityV1.java
@@ -0,0 +1,106 @@
+/*
+ * 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.db.migration;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Flow DB entity from the original database schema in 0.1.0, used for migration purposes.
+ */
+public class FlowEntityV1 {
+
+    private String id;
+
+    private String name;
+
+    private String description;
+
+    private Date created;
+
+    private Date modified;
+
+    private String bucketId;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public Date getModified() {
+        return modified;
+    }
+
+    public void setModified(Date modified) {
+        this.modified = modified;
+    }
+
+    public String getBucketId() {
+        return bucketId;
+    }
+
+    public void setBucketId(String bucketId) {
+        this.bucketId = bucketId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(this.id);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        final FlowEntityV1 other = (FlowEntityV1) obj;
+        return Objects.equals(this.id, other.id);
+    }
+
+}