http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionService.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionService.java
new file mode 100644
index 0000000..3ab8a07
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionService.java
@@ -0,0 +1,130 @@
+/*
+ * 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.extension;
+
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.extension.ExtensionBundle;
+import org.apache.nifi.registry.extension.ExtensionBundleType;
+import org.apache.nifi.registry.extension.ExtensionBundleVersion;
+import org.apache.nifi.registry.extension.ExtensionBundleVersionMetadata;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoArtifact;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoBucket;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoGroup;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoVersionSummary;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+public interface ExtensionService {
+
+    /**
+     * Creates a version of an extension bundle.
+     *
+     * The InputStream is expected to contain the binary contents of a bundle 
in the format specified by bundleType.
+     *
+     * The metadata will be extracted from the bundle and used to determine if 
this is a new version of an existing bundle,
+     * or it will create a new bundle and this as the first version if one 
doesn't already exist.
+     *
+     * @param bucketIdentifier the bucket id
+     * @param bundleType the type of bundle
+     * @param inputStream the binary content of the bundle
+     * @param clientSha256 the SHA-256 hex supplied by the client
+     * @return the ExtensionBundleVersion representing all of the information 
about the bundle
+     * @throws IOException if an error occurs processing the InputStream
+     */
+    ExtensionBundleVersion createExtensionBundleVersion(String 
bucketIdentifier, ExtensionBundleType bundleType,
+                                                        InputStream 
inputStream, String clientSha256) throws IOException;
+
+    /**
+     * Retrieves the extension bundles in the given buckets.
+     *
+     * @param bucketIdentifiers the bucket identifiers
+     * @return the bundles in the given buckets
+     */
+    List<ExtensionBundle> getExtensionBundles(Set<String> bucketIdentifiers);
+
+    /**
+     * Retrieves the extension bundles in the given bucket.
+     *
+     * @param bucketIdentifier the bucket identifier
+     * @return the bundles in the given bucket
+     */
+    List<ExtensionBundle> getExtensionBundlesByBucket(String bucketIdentifier);
+
+    /**
+     * Retrieve the extension bundle with the given id.
+     *
+     * @param extensionBundleIdentifier the extension bundle id
+     * @return the bundle
+     */
+    ExtensionBundle getExtensionBundle(String extensionBundleIdentifier);
+
+    /**
+     * Deletes the given extension bundle and all it's versions.
+     *
+     * @param extensionBundle the extension bundle to delete
+     * @return the deleted bundle
+     */
+    ExtensionBundle deleteExtensionBundle(ExtensionBundle extensionBundle);
+
+    /**
+     * Retrieves the versions of the given extension bundle.
+     *
+     * @param extensionBundleIdentifier the extension bundle id
+     * @return the sorted set of versions for the given bundle
+     */
+    SortedSet<ExtensionBundleVersionMetadata> 
getExtensionBundleVersions(String extensionBundleIdentifier);
+
+    /**
+     * Retrieves the full ExtensionBundleVersion object, including version 
metadata, bundle metadata, and bucket metadata.
+     *
+     * @param versionCoordinate the coordinate of the version
+     * @return the extension bundle version
+     */
+    ExtensionBundleVersion 
getExtensionBundleVersion(ExtensionBundleVersionCoordinate versionCoordinate);
+
+    /**
+     * Writes the binary content of the extension bundle version to the given 
OutputStream.
+     *
+     * @param extensionBundleVersion the version to write the content for
+     * @param out the output stream to write to
+     */
+    void writeExtensionBundleVersionContent(ExtensionBundleVersion 
extensionBundleVersion, OutputStream out);
+
+    /**
+     * Deletes the given version of the extension bundle.
+     *
+     * @param bundleVersion the version to delete
+     * @return the deleted extension bundle version
+     */
+    ExtensionBundleVersion deleteExtensionBundleVersion(ExtensionBundleVersion 
bundleVersion);
+
+    // ----- Extension Repo Methods -----
+
+    SortedSet<ExtensionRepoBucket> getExtensionRepoBuckets(Set<String> 
bucketIds);
+
+    SortedSet<ExtensionRepoGroup> getExtensionRepoGroups(Bucket bucket);
+
+    SortedSet<ExtensionRepoArtifact> getExtensionRepoArtifacts(Bucket bucket, 
String groupId);
+
+    SortedSet<ExtensionRepoVersionSummary> getExtensionRepoVersions(Bucket 
bucket, String groupId, String artifactId);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/StandardExtensionService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/StandardExtensionService.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/StandardExtensionService.java
new file mode 100644
index 0000000..e737b75
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/StandardExtensionService.java
@@ -0,0 +1,620 @@
+/*
+ * 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.extension;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.db.entity.BucketEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntityType;
+import 
org.apache.nifi.registry.db.entity.ExtensionBundleVersionDependencyEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleVersionEntity;
+import org.apache.nifi.registry.exception.ResourceNotFoundException;
+import org.apache.nifi.registry.extension.BundleCoordinate;
+import org.apache.nifi.registry.extension.BundleDetails;
+import org.apache.nifi.registry.extension.BundleExtractor;
+import org.apache.nifi.registry.extension.ExtensionBundle;
+import org.apache.nifi.registry.extension.ExtensionBundleContext;
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider;
+import org.apache.nifi.registry.extension.ExtensionBundleType;
+import org.apache.nifi.registry.extension.ExtensionBundleVersion;
+import org.apache.nifi.registry.extension.ExtensionBundleVersionDependency;
+import org.apache.nifi.registry.extension.ExtensionBundleVersionMetadata;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoArtifact;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoBucket;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoGroup;
+import org.apache.nifi.registry.extension.repo.ExtensionRepoVersionSummary;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import 
org.apache.nifi.registry.provider.extension.StandardExtensionBundleContext;
+import org.apache.nifi.registry.security.authorization.user.NiFiUserUtils;
+import org.apache.nifi.registry.service.DataModelMapper;
+import org.apache.nifi.registry.service.MetadataService;
+import org.apache.nifi.registry.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Service
+public class StandardExtensionService implements ExtensionService {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(StandardExtensionService.class);
+
+    private final MetadataService metadataService;
+    private final Map<ExtensionBundleType, BundleExtractor> extractors;
+    private final ExtensionBundlePersistenceProvider bundlePersistenceProvider;
+    private final Validator validator;
+    private final File extensionsWorkingDir;
+
+    @Autowired
+    public StandardExtensionService(final MetadataService metadataService,
+                                    final Map<ExtensionBundleType, 
BundleExtractor> extractors,
+                                    final ExtensionBundlePersistenceProvider 
bundlePersistenceProvider,
+                                    final Validator validator,
+                                    final NiFiRegistryProperties properties) {
+        this.metadataService = metadataService;
+        this.extractors = extractors;
+        this.bundlePersistenceProvider = bundlePersistenceProvider;
+        this.validator = validator;
+        this.extensionsWorkingDir = properties.getExtensionsWorkingDirectory();
+        Validate.notNull(this.metadataService);
+        Validate.notNull(this.extractors);
+        Validate.notNull(this.bundlePersistenceProvider);
+        Validate.notNull(this.validator);
+        Validate.notNull(this.extensionsWorkingDir);
+    }
+
+    private <T>  void validate(T t, String invalidMessage) {
+        final Set<ConstraintViolation<T>> violations = validator.validate(t);
+        if (violations.size() > 0) {
+            throw new ConstraintViolationException(invalidMessage, violations);
+        }
+    }
+
+    @Override
+    public ExtensionBundleVersion createExtensionBundleVersion(final String 
bucketIdentifier, final ExtensionBundleType bundleType,
+                                                               final 
InputStream inputStream, final String clientSha256) throws IOException {
+        if (StringUtils.isBlank(bucketIdentifier)) {
+            throw new IllegalArgumentException("Bucket identifier cannot be 
null or blank");
+        }
+
+        if (bundleType == null) {
+            throw new IllegalArgumentException("Bundle type cannot be null");
+        }
+
+        if (inputStream == null) {
+            throw new IllegalArgumentException("Extension bundle input stream 
cannot be null");
+        }
+
+        if (!extractors.containsKey(bundleType)) {
+            throw new IllegalArgumentException("No metadata extractor is 
registered for bundle-type: " + bundleType);
+        }
+
+        // ensure the bucket exists
+        final BucketEntity existingBucket = 
metadataService.getBucketById(bucketIdentifier);
+        if (existingBucket == null) {
+            LOGGER.warn("The specified bucket id [{}] does not exist.", 
bucketIdentifier);
+            throw new ResourceNotFoundException("The specified bucket ID does 
not exist in this registry.");
+        }
+
+        // ensure the extensions directory exists and we can read and write to 
it
+        FileUtils.ensureDirectoryExistAndCanReadAndWrite(extensionsWorkingDir);
+
+        final String extensionWorkingFilename = UUID.randomUUID().toString();
+        final File extensionWorkingFile = new File(extensionsWorkingDir, 
extensionWorkingFilename);
+        LOGGER.debug("Writing bundle contents to working directory at {}", new 
Object[]{extensionWorkingFile.getAbsolutePath()});
+
+        try {
+            // write the contents of the input stream to a temporary file in 
the extensions working directory
+            final MessageDigest sha256Digest = DigestUtils.getSha256Digest();
+            try (final DigestInputStream digestInputStream = new 
DigestInputStream(inputStream, sha256Digest);
+                 final OutputStream out = new 
FileOutputStream(extensionWorkingFile)) {
+                IOUtils.copy(digestInputStream, out);
+            }
+
+            // get the hex of the SHA-256 computed by the server and compare 
to the client provided SHA-256, if one was provided
+            final String sha256Hex = 
Hex.encodeHexString(sha256Digest.digest());
+            final boolean sha256Supplied = !StringUtils.isBlank(clientSha256);
+            if (sha256Supplied && !sha256Hex.equalsIgnoreCase(clientSha256)) {
+                LOGGER.error("Client provided SHA-256 of '{}', but server 
calculated '{}'", new Object[]{clientSha256, sha256Hex});
+                throw new IllegalStateException("The SHA-256 of the received 
extension bundle does not match the SHA-256 provided by the client");
+            }
+
+            // extract the details of the bundle from the temp file in the 
working directory
+            final BundleDetails bundleDetails;
+            try (final InputStream in = new 
FileInputStream(extensionWorkingFile)) {
+                final BundleExtractor extractor = extractors.get(bundleType);
+                bundleDetails = extractor.extract(in);
+            }
+
+            final BundleCoordinate bundleCoordinate = 
bundleDetails.getBundleCoordinate();
+            final Set<BundleCoordinate> dependencyCoordinates = 
bundleDetails.getDependencyBundleCoordinates();
+
+            final String groupId = bundleCoordinate.getGroupId();
+            final String artifactId = bundleCoordinate.getArtifactId();
+            final String version = bundleCoordinate.getVersion();
+            LOGGER.debug("Extracted bundle details - '{}' - '{}' - '{}'", new 
Object[]{groupId, artifactId, version});
+
+            // a bundle with the same group, artifact, and version can exist 
in multiple buckets, but only if it contains the same binary content,
+            // we can determine that by comparing the SHA-256 digest of the 
incoming bundle against existing bundles with the same group, artifact, version
+            final List<ExtensionBundleVersionEntity> allExistingVersions = 
metadataService.getExtensionBundleVersionsGlobal(groupId, artifactId, version);
+            for (final ExtensionBundleVersionEntity existingVersionEntity : 
allExistingVersions) {
+                if (!existingVersionEntity.getSha256Hex().equals(sha256Hex)) {
+                    throw new IllegalStateException("Found existing extension 
bundle with same group, artifact, and version, but different SHA-256 
check-sum");
+                }
+            }
+
+            // get the existing extension bundle entity, or create a new one 
if one does not exist in the bucket with the group + artifact
+            final long currentTime = System.currentTimeMillis();
+            final ExtensionBundleEntity extensionBundle = 
getOrCreateExtensionBundle(bucketIdentifier, groupId, artifactId, bundleType, 
currentTime);
+
+            // ensure there isn't already a version of the bundle with the 
same version
+            final ExtensionBundleVersionEntity existingVersion = 
metadataService.getExtensionBundleVersion(bucketIdentifier, groupId, 
artifactId, version);
+            if (existingVersion != null) {
+                LOGGER.warn("The specified version [{}] already exists for 
extension bundle [{}].", new Object[]{version, extensionBundle.getId()});
+                throw new IllegalStateException("The specified version already 
exists for the given extension bundle");
+            }
+
+            // create the version metadata instance and validate it has all 
the required fields
+            final String userIdentity = NiFiUserUtils.getNiFiUserIdentity();
+            final ExtensionBundleVersionMetadata versionMetadata = new 
ExtensionBundleVersionMetadata();
+            versionMetadata.setId(UUID.randomUUID().toString());
+            versionMetadata.setExtensionBundleId(extensionBundle.getId());
+            versionMetadata.setBucketId(bucketIdentifier);
+            versionMetadata.setVersion(version);
+            versionMetadata.setTimestamp(currentTime);
+            versionMetadata.setAuthor(userIdentity);
+            versionMetadata.setSha256(sha256Hex);
+            versionMetadata.setSha256Supplied(sha256Supplied);
+
+            validate(versionMetadata, "Cannot create extension bundle 
version");
+
+            // create the version dependency instances and validate they have 
the required fields
+            final Set<ExtensionBundleVersionDependency> versionDependencies = 
new HashSet<>();
+            for (final BundleCoordinate dependencyCoordinate : 
dependencyCoordinates) {
+                final ExtensionBundleVersionDependency versionDependency = new 
ExtensionBundleVersionDependency();
+                
versionDependency.setGroupId(dependencyCoordinate.getGroupId());
+                
versionDependency.setArtifactId(dependencyCoordinate.getArtifactId());
+                
versionDependency.setVersion(dependencyCoordinate.getVersion());
+
+                validate(versionDependency, "Cannot create extension bundle 
version dependency");
+                versionDependencies.add(versionDependency);
+            }
+
+            // create the bundle version in the metadata db
+            final ExtensionBundleVersionEntity versionEntity = 
DataModelMapper.map(versionMetadata);
+            metadataService.createExtensionBundleVersion(versionEntity);
+
+            // create the bundle version dependencies in the metadata db
+            for (final ExtensionBundleVersionDependency versionDependency : 
versionDependencies) {
+                final ExtensionBundleVersionDependencyEntity 
versionDependencyEntity = DataModelMapper.map(versionDependency);
+                versionDependencyEntity.setId(UUID.randomUUID().toString());
+                
versionDependencyEntity.setExtensionBundleVersionId(versionEntity.getId());
+                metadataService.createDependency(versionDependencyEntity);
+            }
+
+            // persist the content of the bundle to the persistence provider
+            final ExtensionBundleContext context = new 
StandardExtensionBundleContext.Builder()
+                    .bundleType(getProviderBundleType(bundleType))
+                    .bucketId(existingBucket.getId())
+                    .bucketName(existingBucket.getName())
+                    .bundleId(extensionBundle.getId())
+                    .bundleGroupId(extensionBundle.getGroupId())
+                    .bundleArtifactId(extensionBundle.getArtifactId())
+                    .bundleVersion(versionMetadata.getVersion())
+                    .author(versionMetadata.getAuthor())
+                    .timestamp(versionMetadata.getTimestamp())
+                    .build();
+
+            try (final InputStream in = new 
FileInputStream(extensionWorkingFile);
+                 final InputStream bufIn = new BufferedInputStream(in)) {
+                bundlePersistenceProvider.saveBundleVersion(context, bufIn);
+                LOGGER.debug("Bundle saved to persistence provider - '{}' - 
'{}' - '{}'",
+                        new Object[]{groupId, artifactId, version});
+            }
+
+            // get the updated extension bundle so it contains the correct 
version count
+            final ExtensionBundleEntity updatedBundle = 
metadataService.getExtensionBundle(bucketIdentifier, groupId, artifactId);
+
+            // create the full ExtensionBundleVersion instance to return
+            final ExtensionBundleVersion extensionBundleVersion = new 
ExtensionBundleVersion();
+            extensionBundleVersion.setVersionMetadata(versionMetadata);
+            
extensionBundleVersion.setExtensionBundle(DataModelMapper.map(existingBucket, 
updatedBundle));
+            
extensionBundleVersion.setBucket(DataModelMapper.map(existingBucket));
+            extensionBundleVersion.setDependencies(versionDependencies);
+            return extensionBundleVersion;
+
+        } finally {
+            if (extensionWorkingFile.exists()) {
+                try {
+                    extensionWorkingFile.delete();
+                } catch (Exception e) {
+                    LOGGER.warn("Error removing temporary extension bundle 
file at {}",
+                            new 
Object[]{extensionWorkingFile.getAbsolutePath()});
+                }
+            }
+        }
+    }
+
+    private ExtensionBundleEntity getOrCreateExtensionBundle(final String 
bucketId, final String groupId,
+                                                             final String 
artifactId, final ExtensionBundleType bundleType,
+                                                             final long 
currentTime) {
+        ExtensionBundleEntity existingBundleEntity = 
metadataService.getExtensionBundle(bucketId, groupId, artifactId);
+        if (existingBundleEntity == null) {
+            final ExtensionBundle bundle = new ExtensionBundle();
+            bundle.setIdentifier(UUID.randomUUID().toString());
+            bundle.setBucketIdentifier(bucketId);
+            bundle.setName(groupId + ":" + artifactId);
+            bundle.setGroupId(groupId);
+            bundle.setArtifactId(artifactId);
+            bundle.setBundleType(bundleType);
+            bundle.setCreatedTimestamp(currentTime);
+            bundle.setModifiedTimestamp(currentTime);
+
+            validate(bundle, "Cannot create extension bundle");
+            existingBundleEntity = 
metadataService.createExtensionBundle(DataModelMapper.map(bundle));
+        } else {
+            final ExtensionBundleEntityType bundleEntityType = 
DataModelMapper.map(bundleType);
+            if (bundleEntityType != existingBundleEntity.getBundleType()) {
+                throw new IllegalStateException("A bundle already exists with 
the same group id and artifact id, but a different bundle type");
+            }
+        }
+
+        return existingBundleEntity;
+    }
+
+    private ExtensionBundleContext.BundleType getProviderBundleType(final 
ExtensionBundleType bundleType) {
+        switch (bundleType) {
+            case NIFI_NAR:
+                return ExtensionBundleContext.BundleType.NIFI_NAR;
+            case MINIFI_CPP:
+                return ExtensionBundleContext.BundleType.MINIFI_CPP;
+            default:
+                throw new IllegalArgumentException("Unknown bundle type: " + 
bundleType.toString());
+        }
+    }
+
+    @Override
+    public List<ExtensionBundle> getExtensionBundles(Set<String> 
bucketIdentifiers) {
+        if (bucketIdentifiers == null) {
+            throw new IllegalArgumentException("Bucket identifiers cannot be 
null");
+        }
+
+        final List<ExtensionBundleEntity> bundleEntities = 
metadataService.getExtensionBundles(bucketIdentifiers);
+        return bundleEntities.stream().map(b -> DataModelMapper.map(null, 
b)).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<ExtensionBundle> getExtensionBundlesByBucket(final String 
bucketIdentifier) {
+        if (StringUtils.isBlank(bucketIdentifier)) {
+            throw new IllegalArgumentException("Bucket identifier cannot be 
null or blank");
+        }
+
+        // ensure the bucket exists
+        final BucketEntity existingBucket = 
metadataService.getBucketById(bucketIdentifier);
+        if (existingBucket == null) {
+            LOGGER.warn("The specified bucket id [{}] does not exist.", 
bucketIdentifier);
+            throw new ResourceNotFoundException("The specified bucket ID does 
not exist in this registry.");
+        }
+
+        final List<ExtensionBundleEntity> bundleEntities = 
metadataService.getExtensionBundlesByBucket(bucketIdentifier);
+        return bundleEntities.stream().map(b -> 
DataModelMapper.map(existingBucket, b)).collect(Collectors.toList());
+    }
+
+    @Override
+    public ExtensionBundle getExtensionBundle(final String 
extensionBundleIdentifier) {
+        if (StringUtils.isBlank(extensionBundleIdentifier)) {
+            throw new IllegalArgumentException("Extension bundle identifier 
cannot be null or blank");
+        }
+
+        final ExtensionBundleEntity existingBundle = 
metadataService.getExtensionBundle(extensionBundleIdentifier);
+        if (existingBundle == null) {
+            LOGGER.warn("The specified extension bundle id [{}] does not 
exist.", extensionBundleIdentifier);
+            throw new ResourceNotFoundException("The specified extension 
bundle ID does not exist.");
+        }
+
+        final BucketEntity existingBucket = 
metadataService.getBucketById(existingBundle.getBucketId());
+        return DataModelMapper.map(existingBucket, existingBundle);
+    }
+
+    @Override
+    public ExtensionBundle deleteExtensionBundle(final ExtensionBundle 
extensionBundle) {
+        if (extensionBundle == null) {
+            throw new IllegalArgumentException("Extension bundle cannot be 
null");
+        }
+
+        // delete the bundle from the database
+        metadataService.deleteExtensionBundle(extensionBundle.getIdentifier());
+
+        // delete all content associated with the bundle in the persistence 
provider
+        bundlePersistenceProvider.deleteAllBundleVersions(
+                extensionBundle.getBucketIdentifier(),
+                extensionBundle.getBucketName(),
+                extensionBundle.getGroupId(),
+                extensionBundle.getArtifactId());
+
+        return extensionBundle;
+    }
+
+    @Override
+    public SortedSet<ExtensionBundleVersionMetadata> 
getExtensionBundleVersions(final String extensionBundleIdentifier) {
+        if (StringUtils.isBlank(extensionBundleIdentifier)) {
+            throw new IllegalArgumentException("Extension bundle identifier 
cannot be null or blank");
+        }
+
+        // ensure the bundle exists
+        final ExtensionBundleEntity existingBundle = 
metadataService.getExtensionBundle(extensionBundleIdentifier);
+        if (existingBundle == null) {
+            LOGGER.warn("The specified extension bundle id [{}] does not 
exist.", extensionBundleIdentifier);
+            throw new ResourceNotFoundException("The specified extension 
bundle ID does not exist in this bucket.");
+        }
+
+        return getExtensionBundleVersionsSet(existingBundle);
+    }
+
+    private SortedSet<ExtensionBundleVersionMetadata> 
getExtensionBundleVersionsSet(ExtensionBundleEntity existingBundle) {
+        final SortedSet<ExtensionBundleVersionMetadata> sortedVersions = new 
TreeSet<>(Collections.reverseOrder());
+
+        final List<ExtensionBundleVersionEntity> existingVersions = 
metadataService.getExtensionBundleVersions(existingBundle.getId());
+        if (existingVersions != null) {
+            final BucketEntity existingBucket = 
metadataService.getBucketById(existingBundle.getBucketId());
+            existingVersions.stream().forEach(s -> 
sortedVersions.add(DataModelMapper.map(existingBucket, s)));
+        }
+
+        return sortedVersions;
+    }
+
+    @Override
+    public ExtensionBundleVersion 
getExtensionBundleVersion(ExtensionBundleVersionCoordinate versionCoordinate) {
+        if (versionCoordinate == null) {
+            throw new IllegalArgumentException("Extension bundle version 
coordinate cannot be null");
+        }
+
+        // ensure the bucket exists
+        final BucketEntity existingBucket = 
metadataService.getBucketById(versionCoordinate.getBucketId());
+        if (existingBucket == null) {
+            LOGGER.warn("The specified bucket id [{}] does not exist.", 
versionCoordinate.getBucketId());
+            throw new ResourceNotFoundException("The specified bucket ID does 
not exist in this registry.");
+        }
+
+        // ensure the bundle exists
+        final ExtensionBundleEntity existingBundle = 
metadataService.getExtensionBundle(
+                versionCoordinate.getBucketId(),
+                versionCoordinate.getGroupId(),
+                versionCoordinate.getArtifactId());
+
+        if (existingBundle == null) {
+            LOGGER.warn("The specified extension bundle [{}] does not exist.", 
versionCoordinate.toString());
+            throw new ResourceNotFoundException("The specified extension 
bundle does not exist in this bucket.");
+        }
+
+        //ensure the version of the bundle exists
+        final ExtensionBundleVersionEntity existingVersion = 
metadataService.getExtensionBundleVersion(
+                versionCoordinate.getBucketId(),
+                versionCoordinate.getGroupId(),
+                versionCoordinate.getArtifactId(),
+                versionCoordinate.getVersion());
+
+        if (existingVersion == null) {
+            LOGGER.warn("The specified extension bundle version [{}] does not 
exist.", versionCoordinate.toString());
+            throw new ResourceNotFoundException("The specified extension 
bundle version does not exist in this bucket.");
+        }
+
+        // get the dependencies for the bundle version
+        final List<ExtensionBundleVersionDependencyEntity> 
existingVersionDependencies = metadataService
+                .getDependenciesForBundleVersion(existingVersion.getId());
+
+        // convert the dependency db entities
+        final Set<ExtensionBundleVersionDependency> dependencies = 
existingVersionDependencies.stream()
+                .map(d -> DataModelMapper.map(d))
+                .collect(Collectors.toSet());
+
+        // create the full ExtensionBundleVersion instance to return
+        final ExtensionBundleVersion extensionBundleVersion = new 
ExtensionBundleVersion();
+        
extensionBundleVersion.setVersionMetadata(DataModelMapper.map(existingBucket, 
existingVersion));
+        
extensionBundleVersion.setExtensionBundle(DataModelMapper.map(existingBucket, 
existingBundle));
+        extensionBundleVersion.setBucket(DataModelMapper.map(existingBucket));
+        extensionBundleVersion.setDependencies(dependencies);
+        return extensionBundleVersion;
+    }
+
+    @Override
+    public void writeExtensionBundleVersionContent(final 
ExtensionBundleVersion bundleVersion, final OutputStream out) {
+        // get the content from the persistence provider and write it to the 
output stream
+        final ExtensionBundleContext context = 
getExtensionBundleContext(bundleVersion);
+        bundlePersistenceProvider.getBundleVersion(context, out);
+    }
+
+    @Override
+    public ExtensionBundleVersion deleteExtensionBundleVersion(final 
ExtensionBundleVersion bundleVersion) {
+        if (bundleVersion == null) {
+            throw new IllegalArgumentException("Extension bundle version 
cannot be null");
+        }
+
+        // delete from the metadata db
+        final String extensionBundleVersionId = 
bundleVersion.getVersionMetadata().getId();
+        metadataService.deleteExtensionBundleVersion(extensionBundleVersionId);
+
+        // delete content associated with the bundle version in the 
persistence provider
+        final ExtensionBundleContext context = new 
StandardExtensionBundleContext.Builder()
+                
.bundleType(getProviderBundleType(bundleVersion.getExtensionBundle().getBundleType()))
+                .bucketId(bundleVersion.getBucket().getIdentifier())
+                .bucketName(bundleVersion.getBucket().getName())
+                .bundleId(bundleVersion.getExtensionBundle().getIdentifier())
+                .bundleGroupId(bundleVersion.getExtensionBundle().getGroupId())
+                
.bundleArtifactId(bundleVersion.getExtensionBundle().getArtifactId())
+                .bundleVersion(bundleVersion.getVersionMetadata().getVersion())
+                .author(bundleVersion.getVersionMetadata().getAuthor())
+                .timestamp(bundleVersion.getVersionMetadata().getTimestamp())
+                .build();
+
+        bundlePersistenceProvider.deleteBundleVersion(context);
+
+        return bundleVersion;
+    }
+
+    // ------ Extension Repository Methods -------
+
+    @Override
+    public SortedSet<ExtensionRepoBucket> getExtensionRepoBuckets(final 
Set<String> bucketIds) {
+        if (bucketIds == null) {
+            throw new IllegalArgumentException("Bucket ids cannot be null");
+        }
+
+        if (bucketIds.isEmpty()) {
+            return new TreeSet<>();
+        }
+
+        final SortedSet<ExtensionRepoBucket> repoBuckets = new TreeSet<>();
+
+        final List<BucketEntity> buckets = 
metadataService.getBuckets(bucketIds);
+        buckets.forEach(b -> {
+            final ExtensionRepoBucket repoBucket = new ExtensionRepoBucket();
+            repoBucket.setBucketName(b.getName());
+            repoBuckets.add(repoBucket);
+        });
+
+        return repoBuckets;
+    }
+
+    @Override
+    public SortedSet<ExtensionRepoGroup> getExtensionRepoGroups(final Bucket 
bucket) {
+        if (bucket == null) {
+            throw new IllegalArgumentException("Bucket cannot be null");
+        }
+
+        final SortedSet<ExtensionRepoGroup> repoGroups = new TreeSet<>();
+
+        final List<ExtensionBundleEntity> bundleEntities = 
metadataService.getExtensionBundlesByBucket(bucket.getIdentifier());
+        bundleEntities.forEach(b -> {
+            final ExtensionRepoGroup repoGroup = new ExtensionRepoGroup();
+            repoGroup.setBucketName(bucket.getName());
+            repoGroup.setGroupId(b.getGroupId());
+            repoGroups.add(repoGroup);
+        });
+
+        return repoGroups;
+    }
+
+    @Override
+    public SortedSet<ExtensionRepoArtifact> getExtensionRepoArtifacts(final 
Bucket bucket, final String groupId) {
+        if (bucket == null) {
+            throw new IllegalArgumentException("Bucket cannot be null");
+        }
+
+        if (StringUtils.isBlank(groupId)) {
+            throw new IllegalArgumentException("Group id cannot be null or 
blank");
+        }
+
+        final SortedSet<ExtensionRepoArtifact> repoArtifacts = new TreeSet<>();
+
+        final List<ExtensionBundleEntity> bundleEntities = 
metadataService.getExtensionBundlesByBucketAndGroup(bucket.getIdentifier(), 
groupId);
+        bundleEntities.forEach(b -> {
+            final ExtensionRepoArtifact repoArtifact = new 
ExtensionRepoArtifact();
+            repoArtifact.setBucketName(bucket.getName());
+            repoArtifact.setGroupId(b.getGroupId());
+            repoArtifact.setArtifactId(b.getArtifactId());
+            repoArtifacts.add(repoArtifact);
+        });
+
+        return repoArtifacts;
+    }
+
+    @Override
+    public SortedSet<ExtensionRepoVersionSummary> 
getExtensionRepoVersions(final Bucket bucket, final String groupId, final 
String artifactId) {
+        if (bucket == null) {
+            throw new IllegalArgumentException("Bucket cannot be null");
+        }
+
+        if (StringUtils.isBlank(groupId)) {
+            throw new IllegalArgumentException("Group id cannot be null or 
blank");
+        }
+
+        if (StringUtils.isBlank(artifactId)) {
+            throw new IllegalArgumentException("Artifact id cannot be null or 
blank");
+        }
+
+        final SortedSet<ExtensionRepoVersionSummary> repoVersions = new 
TreeSet<>();
+
+        final List<ExtensionBundleVersionEntity> versionEntities = 
metadataService.getExtensionBundleVersions(bucket.getIdentifier(), groupId, 
artifactId);
+        if (!versionEntities.isEmpty()) {
+            final ExtensionBundleEntity bundleEntity = 
metadataService.getExtensionBundle(bucket.getIdentifier(), groupId, artifactId);
+            if (bundleEntity == null) {
+                // should never happen if the list of versions is not empty, 
but just in case
+                throw new ResourceNotFoundException("The specified extension 
bundle does not exist in this bucket");
+            }
+
+            versionEntities.forEach(v -> {
+                final ExtensionRepoVersionSummary repoVersion = new 
ExtensionRepoVersionSummary();
+                repoVersion.setBucketName(bucket.getName());
+                repoVersion.setGroupId(bundleEntity.getGroupId());
+                repoVersion.setArtifactId(bundleEntity.getArtifactId());
+                repoVersion.setVersion(v.getVersion());
+                repoVersions.add(repoVersion);
+            });
+        }
+
+        return repoVersions;
+    }
+
+    // ------ Helper Methods -------
+
+    private ExtensionBundleContext getExtensionBundleContext(final 
ExtensionBundleVersion bundleVersion) {
+        return getExtensionBundleContext(bundleVersion.getBucket(), 
bundleVersion.getExtensionBundle(), bundleVersion.getVersionMetadata());
+    }
+
+    private ExtensionBundleContext getExtensionBundleContext(final Bucket 
bucket, final ExtensionBundle bundle,
+                                                             final 
ExtensionBundleVersionMetadata bundleVersionMetadata) {
+        return new StandardExtensionBundleContext.Builder()
+                .bundleType(getProviderBundleType(bundle.getBundleType()))
+                .bucketId(bucket.getIdentifier())
+                .bucketName(bucket.getName())
+                .bundleId(bundle.getIdentifier())
+                .bundleGroupId(bundle.getGroupId())
+                .bundleArtifactId(bundle.getArtifactId())
+                .bundleVersion(bundleVersionMetadata.getVersion())
+                .author(bundleVersionMetadata.getAuthor())
+                .timestamp(bundleVersionMetadata.getTimestamp())
+                .build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider
 
b/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider
new file mode 100644
index 0000000..00dcbb8
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/resources/META-INF/services/org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider
@@ -0,0 +1,15 @@
+# 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.
+org.apache.nifi.registry.provider.extension.FileSystemExtensionBundlePersistenceProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V3__AddExtensions.sql
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V3__AddExtensions.sql
 
b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V3__AddExtensions.sql
new file mode 100644
index 0000000..da66cd1
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V3__AddExtensions.sql
@@ -0,0 +1,71 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+CREATE TABLE EXTENSION_BUNDLE (
+    ID VARCHAR(50) NOT NULL,
+    BUCKET_ID VARCHAR(50) NOT NULL,
+    BUNDLE_TYPE VARCHAR(200) NOT NULL,
+    GROUP_ID VARCHAR(500) NOT NULL,
+    ARTIFACT_ID VARCHAR(500) NOT NULL,
+    CONSTRAINT PK__EXTENSION_BUNDLE_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_BUNDLE_BUCKET_ITEM_ID FOREIGN KEY (ID) REFERENCES 
BUCKET_ITEM(ID) ON DELETE CASCADE,
+    CONSTRAINT FK__EXTENSION_BUNDLE_BUCKET_ID FOREIGN KEY(BUCKET_ID) 
REFERENCES BUCKET(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_BUNDLE_BUCKET_GROUP_ARTIFACT UNIQUE 
(BUCKET_ID, GROUP_ID, ARTIFACT_ID)
+);
+
+CREATE TABLE EXTENSION_BUNDLE_VERSION (
+    ID VARCHAR(50) NOT NULL,
+    EXTENSION_BUNDLE_ID VARCHAR(50) NOT NULL,
+    VERSION VARCHAR(100) NOT NULL,
+    CREATED TIMESTAMP NOT NULL,
+    CREATED_BY VARCHAR(4096) NOT NULL,
+    DESCRIPTION TEXT,
+    SHA_256_HEX VARCHAR(512) NOT NULL,
+    SHA_256_SUPPLIED INT NOT NULL,
+    CONSTRAINT PK__EXTENSION_BUNDLE_VERSION_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_BUNDLE_VERSION_EXTENSION_BUNDLE_ID FOREIGN KEY 
(EXTENSION_BUNDLE_ID) REFERENCES EXTENSION_BUNDLE(ID) ON DELETE CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_BUNDLE_VERSION_EXTENSION_BUNDLE_ID_VERSION 
UNIQUE (EXTENSION_BUNDLE_ID, VERSION)
+);
+
+CREATE TABLE EXTENSION_BUNDLE_VERSION_DEPENDENCY (
+    ID VARCHAR(50) NOT NULL,
+    EXTENSION_BUNDLE_VERSION_ID VARCHAR(50) NOT NULL,
+    GROUP_ID VARCHAR(500) NOT NULL,
+    ARTIFACT_ID VARCHAR(500) NOT NULL,
+    VERSION VARCHAR(100) NOT NULL,
+    CONSTRAINT PK__EXTENSION_BUNDLE_VERSION_DEPENDENCY_ID PRIMARY KEY (ID),
+    CONSTRAINT 
FK__EXTENSION_BUNDLE_VERSION_DEPENDENCY_EXTENSION_BUNDLE_VERSION_ID FOREIGN KEY 
(EXTENSION_BUNDLE_VERSION_ID) REFERENCES EXTENSION_BUNDLE_VERSION(ID) ON DELETE 
CASCADE,
+    CONSTRAINT 
UNIQUE__EXTENSION_BUNDLE_VERSION_DEPENDENCY_BUNDLE_ID_GROUP_ARTIFACT_VERSION 
UNIQUE (EXTENSION_BUNDLE_VERSION_ID, GROUP_ID, ARTIFACT_ID, VERSION)
+);
+
+CREATE TABLE EXTENSION (
+    ID VARCHAR(50) NOT NULL,
+    EXTENSION_BUNDLE_VERSION_ID VARCHAR(50) NOT NULL,
+    TYPE VARCHAR(500) NOT NULL,
+    TYPE_DESCRIPTION TEXT NOT NULL,
+    IS_RESTRICTED INT NOT NULL,
+    CATEGORY VARCHAR(100) NOT NULL,
+    TAGS TEXT,
+    CONSTRAINT PK__EXTENSION_ID PRIMARY KEY (ID),
+    CONSTRAINT FK__EXTENSION_EXTENSION_BUNDLE_VERSION_ID FOREIGN KEY 
(EXTENSION_BUNDLE_VERSION_ID) REFERENCES EXTENSION_BUNDLE_VERSION(ID) ON DELETE 
CASCADE,
+    CONSTRAINT UNIQUE__EXTENSION_EXTENSION_BUNDLE_VERSION_ID_AND_TYPE UNIQUE 
(EXTENSION_BUNDLE_VERSION_ID, TYPE)
+);
+
+CREATE TABLE EXTENSION_TAG (
+    EXTENSION_ID VARCHAR(50) NOT NULL,
+    TAG VARCHAR(200) NOT NULL,
+    CONSTRAINT PK__EXTENSION_TAG_EXTENSION_ID_AND_TAG PRIMARY KEY 
(EXTENSION_ID, TAG),
+    CONSTRAINT FK__EXTENSION_TAG_EXTENSION_ID FOREIGN KEY (EXTENSION_ID) 
REFERENCES EXTENSION(ID) ON DELETE CASCADE
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V4__AddCascadeOnDelete.sql
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V4__AddCascadeOnDelete.sql
 
b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V4__AddCascadeOnDelete.sql
new file mode 100644
index 0000000..5b0e6c6
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/resources/db/migration/V4__AddCascadeOnDelete.sql
@@ -0,0 +1,23 @@
+-- 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.
+
+ALTER TABLE BUCKET_ITEM DROP CONSTRAINT FK__BUCKET_ITEM_BUCKET_ID;
+ALTER TABLE BUCKET_ITEM ADD CONSTRAINT FK__BUCKET_ITEM_BUCKET_ID FOREIGN KEY 
(BUCKET_ID) REFERENCES BUCKET(ID) ON DELETE CASCADE;
+
+ALTER TABLE FLOW DROP CONSTRAINT FK__FLOW_BUCKET_ITEM_ID;
+ALTER TABLE FLOW ADD CONSTRAINT FK__FLOW_BUCKET_ITEM_ID FOREIGN KEY (ID) 
REFERENCES BUCKET_ITEM(ID) ON DELETE CASCADE;
+
+ALTER TABLE FLOW_SNAPSHOT DROP CONSTRAINT FK__FLOW_SNAPSHOT_FLOW_ID;
+ALTER TABLE FLOW_SNAPSHOT ADD CONSTRAINT FK__FLOW_SNAPSHOT_FLOW_ID FOREIGN KEY 
(FLOW_ID) REFERENCES FLOW(ID) ON DELETE CASCADE;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd 
b/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
index ce82dcc..4e9f5d1 100644
--- a/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
+++ b/nifi-registry-core/nifi-registry-framework/src/main/xsd/providers.xsd
@@ -44,6 +44,7 @@
             <xs:sequence>
                 <xs:element name="flowPersistenceProvider" type="Provider" 
minOccurs="1" maxOccurs="1" />
                 <xs:element name="eventHookProvider" type="Provider" 
minOccurs="0" maxOccurs="unbounded" />
+                <xs:element name="extensionBundlePersistenceProvider" 
type="Provider" minOccurs="1" maxOccurs="1" />
             </xs:sequence>
         </xs:complexType>
     </xs:element>

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
index 35ba757..a2bacd4 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/TestDatabaseMetadataService.java
@@ -19,6 +19,12 @@ 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.ExtensionBundleEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntityType;
+import 
org.apache.nifi.registry.db.entity.ExtensionBundleVersionDependencyEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleVersionEntity;
+import org.apache.nifi.registry.db.entity.ExtensionEntity;
+import org.apache.nifi.registry.db.entity.ExtensionEntityCategory;
 import org.apache.nifi.registry.db.entity.FlowEntity;
 import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
 import org.apache.nifi.registry.service.MetadataService;
@@ -30,9 +36,11 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -383,4 +391,448 @@ public class TestDatabaseMetadataService extends 
DatabaseBaseTest {
         assertNull(deletedEntity);
     }
 
+    //----------------- Extension Bundles ---------------------------------
+
+    @Test
+    public void testGetExtensionBundleById() {
+        final ExtensionBundleEntity entity = 
metadataService.getExtensionBundle("eb1");
+        assertNotNull(entity);
+
+        assertEquals("eb1", entity.getId());
+        assertEquals("nifi-example-processors-nar", entity.getName());
+        assertEquals("Example processors bundle", entity.getDescription());
+        assertNotNull(entity.getCreated());
+        assertNotNull(entity.getModified());
+        assertEquals(BucketItemEntityType.EXTENSION_BUNDLE, entity.getType());
+        assertEquals("3", entity.getBucketId());
+
+        assertEquals(ExtensionBundleEntityType.NIFI_NAR, 
entity.getBundleType());
+
+        assertEquals("org.apache.nifi", entity.getGroupId());
+        assertEquals("nifi-example-processors-nar", entity.getArtifactId());
+    }
+
+    @Test
+    public void testGetExtensionBundleDoesNotExist() {
+        final ExtensionBundleEntity entity = 
metadataService.getExtensionBundle("does-not-exist");
+        assertNull(entity);
+    }
+
+    @Test
+    public void testGetExtensionBundleByGroupArtifact() {
+        final String bucketId = "3";
+        final String group = "org.apache.nifi";
+        final String artifact = "nifi-example-service-api-nar";
+
+        final ExtensionBundleEntity entity = 
metadataService.getExtensionBundle(bucketId, group, artifact);
+        assertNotNull(entity);
+        assertEquals(bucketId, entity.getBucketId());
+
+        assertEquals(group, entity.getGroupId());
+        assertEquals(artifact, entity.getArtifactId());
+    }
+
+    @Test
+    public void testGetExtensionBundleByGroupArtifactDoesNotExist() {
+        final String bucketId = "3";
+        final String group = "org.apache.nifi";
+        final String artifact = "does-not-exist";
+
+        final ExtensionBundleEntity entity = 
metadataService.getExtensionBundle(bucketId, group, artifact);
+        assertNull(entity);
+    }
+
+    @Test
+    public void testGetExtensionBundles() {
+        final Set<String> bucketIds = new HashSet<>();
+        bucketIds.add("1");
+        bucketIds.add("2");
+        bucketIds.add("3");
+
+        final List<ExtensionBundleEntity> bundles = 
metadataService.getExtensionBundles(bucketIds);
+        assertNotNull(bundles);
+        assertEquals(3, bundles.size());
+
+        bundles.forEach(b -> {
+            assertTrue(b.getVersionCount() > 0);
+            assertNotNull(b.getBucketName());
+        });
+    }
+
+    @Test
+    public void testGetExtensionBundlesByBucket() {
+        final List<ExtensionBundleEntity> bundles = 
metadataService.getExtensionBundlesByBucket("3");
+        assertNotNull(bundles);
+        assertEquals(3, bundles.size());
+
+        final List<ExtensionBundleEntity> bundles2 = 
metadataService.getExtensionBundlesByBucket("6");
+        assertNotNull(bundles2);
+        assertEquals(0, bundles2.size());
+    }
+
+    @Test
+    public void testGetExtensionBundlesByBucketAndGroup() {
+        final List<ExtensionBundleEntity> bundles = 
metadataService.getExtensionBundlesByBucketAndGroup("3", "org.apache.nifi");
+        assertNotNull(bundles);
+        assertEquals(3, bundles.size());
+
+        final List<ExtensionBundleEntity> bundles2 = 
metadataService.getExtensionBundlesByBucketAndGroup("3", "does-not-exist");
+        assertNotNull(bundles2);
+        assertEquals(0, bundles2.size());
+    }
+
+    @Test
+    public void testCreateExtensionBundle() {
+        final ExtensionBundleEntity entity = new ExtensionBundleEntity();
+        entity.setId(UUID.randomUUID().toString());
+        entity.setBucketId("3");
+        entity.setName("nifi-foo-nar");
+        entity.setDescription("This is foo nar");
+        entity.setCreated(new Date());
+        entity.setModified(new Date());
+        entity.setGroupId("org.apache.nifi");
+        entity.setArtifactId("nifi-foo-nar");
+        entity.setBundleType(ExtensionBundleEntityType.NIFI_NAR);
+
+        final ExtensionBundleEntity createdEntity = 
metadataService.createExtensionBundle(entity);
+        assertNotNull(createdEntity);
+
+        final List<ExtensionBundleEntity> bundles = 
metadataService.getExtensionBundlesByBucket("3");
+        assertNotNull(bundles);
+        assertEquals(4, bundles.size());
+    }
+
+    @Test
+    public void testDeleteExtensionBundle() {
+        final List<ExtensionBundleEntity> bundles = 
metadataService.getExtensionBundlesByBucket("3");
+        assertNotNull(bundles);
+        assertEquals(3, bundles.size());
+
+        final ExtensionBundleEntity existingBundle = bundles.get(0);
+        metadataService.deleteExtensionBundle(existingBundle);
+
+        final ExtensionBundleEntity deletedBundle = 
metadataService.getExtensionBundle(existingBundle.getId());
+        assertNull(deletedBundle);
+
+        final List<ExtensionBundleEntity> bundlesAfterDelete = 
metadataService.getExtensionBundlesByBucket("3");
+        assertNotNull(bundlesAfterDelete);
+        assertEquals(2, bundlesAfterDelete.size());
+    }
+
+    @Test
+    public void testDeleteBucketWithExtensionBundles() {
+        final List<ExtensionBundleEntity> bundles = 
metadataService.getExtensionBundlesByBucket("3");
+        assertNotNull(bundles);
+        assertEquals(3, bundles.size());
+
+        final BucketEntity bucket = metadataService.getBucketById("3");
+        assertNotNull(bucket);
+        metadataService.deleteBucket(bucket);
+
+        final List<ExtensionBundleEntity> bundlesAfterDelete = 
metadataService.getExtensionBundlesByBucket("3");
+        assertNotNull(bundlesAfterDelete);
+        assertEquals(0, bundlesAfterDelete.size());
+    }
+
+    //----------------- Extension Bundle Versions 
---------------------------------
+
+    @Test
+    public void testCreateExtensionBundleVersion() {
+        final ExtensionBundleVersionEntity bundleVersion = new 
ExtensionBundleVersionEntity();
+        bundleVersion.setId(UUID.randomUUID().toString());
+        bundleVersion.setExtensionBundleId("eb1");
+        bundleVersion.setVersion("1.1.0");
+        bundleVersion.setCreated(new Date());
+        bundleVersion.setCreatedBy("user2");
+        bundleVersion.setDescription("This is v1.1.0");
+        bundleVersion.setSha256Hex("123456789");
+        bundleVersion.setSha256Supplied(false);
+
+        metadataService.createExtensionBundleVersion(bundleVersion);
+
+        final ExtensionBundleVersionEntity createdBundleVersion = 
metadataService.getExtensionBundleVersion("eb1", "1.1.0");
+        assertNotNull(createdBundleVersion);
+        assertEquals(bundleVersion.getId(), createdBundleVersion.getId());
+        assertFalse(bundleVersion.getSha256Supplied());
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionByBundleIdAndVersion() {
+        final ExtensionBundleVersionEntity bundleVersion = 
metadataService.getExtensionBundleVersion("eb1", "1.0.0");
+        assertNotNull(bundleVersion);
+        assertEquals("eb1-v1", bundleVersion.getId());
+        assertEquals("eb1", bundleVersion.getExtensionBundleId());
+        assertEquals("1.0.0", bundleVersion.getVersion());
+        assertNotNull(bundleVersion.getCreated());
+        assertEquals("user1", bundleVersion.getCreatedBy());
+        assertEquals("First version of eb1", bundleVersion.getDescription());
+        assertTrue(bundleVersion.getSha256Supplied());
+    }
+
+    @Test
+    public void 
testGetExtensionBundleVersionByBundleIdAndVersionDoesNotExist() {
+        final ExtensionBundleVersionEntity bundleVersion = 
metadataService.getExtensionBundleVersion("does-not-exist", "1.0.0");
+        assertNull(bundleVersion);
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionByBucketGroupArtifactVersion() {
+        final String bucketId = "3";
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "nifi-example-processors-nar";
+        final String version = "1.0.0";
+
+        final ExtensionBundleVersionEntity bundleVersion = 
metadataService.getExtensionBundleVersion(bucketId, groupId, artifactId, 
version);
+        assertNotNull(bundleVersion);
+        assertEquals("eb1-v1", bundleVersion.getId());
+        assertTrue(bundleVersion.getSha256Supplied());
+    }
+
+    @Test
+    public void 
testGetExtensionBundleVersionByBucketGroupArtifactVersionWhenDoesNotExist() {
+        final String bucketId = "3";
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "nifi-example-processors-nar";
+        final String version = "FOO";
+
+        final ExtensionBundleVersionEntity bundleVersion = 
metadataService.getExtensionBundleVersion(bucketId, groupId, artifactId, 
version);
+        assertNull(bundleVersion);
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionsByBundleId() {
+        final List<ExtensionBundleVersionEntity> bundleVersions = 
metadataService.getExtensionBundleVersions("eb1");
+        assertNotNull(bundleVersions);
+        assertEquals(1, bundleVersions.size());
+
+        final ExtensionBundleVersionEntity bundleVersion = 
bundleVersions.get(0);
+        assertEquals("eb1", bundleVersion.getExtensionBundleId());
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionsByBundleIdWhenDoesNotExist() {
+        final List<ExtensionBundleVersionEntity> bundleVersions = 
metadataService.getExtensionBundleVersions("does-not-exist");
+        assertNotNull(bundleVersions);
+        assertEquals(0, bundleVersions.size());
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionsByBucketGroupArtifact() {
+        final String bucketId = "3";
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "nifi-example-processors-nar";
+
+        final List<ExtensionBundleVersionEntity> bundleVersions = 
metadataService.getExtensionBundleVersions(bucketId, groupId, artifactId);
+        assertNotNull(bundleVersions);
+        assertEquals(1, bundleVersions.size());
+
+        final ExtensionBundleVersionEntity bundleVersion = 
bundleVersions.get(0);
+        assertEquals("eb1-v1", bundleVersion.getId());
+    }
+
+    @Test
+    public void 
testGetExtensionBundleVersionsByBucketGroupArtifactWhenDoesNotExist() {
+        final String bucketId = "3";
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "does-not-exist";
+
+        final List<ExtensionBundleVersionEntity> bundleVersions = 
metadataService.getExtensionBundleVersions(bucketId, groupId, artifactId);
+        assertNotNull(bundleVersions);
+        assertEquals(0, bundleVersions.size());
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionsGlobal() {
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "nifi-example-processors-nar";
+        final String version = "1.0.0";
+
+        final List<ExtensionBundleVersionEntity> bundleVersions = 
metadataService.getExtensionBundleVersionsGlobal(groupId, artifactId, version);
+        assertNotNull(bundleVersions);
+        assertEquals(1, bundleVersions.size());
+
+        final ExtensionBundleVersionEntity bundleVersion = 
bundleVersions.get(0);
+        assertEquals("eb1-v1", bundleVersion.getId());
+    }
+
+    @Test
+    public void testDeleteExtensionBundleVersion() {
+        final ExtensionBundleVersionEntity bundleVersion = 
metadataService.getExtensionBundleVersion("eb1", "1.0.0");
+        assertNotNull(bundleVersion);
+
+        metadataService.deleteExtensionBundleVersion(bundleVersion);
+
+        final ExtensionBundleVersionEntity deletedBundleVersion = 
metadataService.getExtensionBundleVersion("eb1", "1.0.0");
+        assertNull(deletedBundleVersion);
+    }
+
+    // ---------- Extension Bundle Version Dependencies ------------
+
+    @Test
+    public void testCreateExtensionBundleVersionDependency() {
+        final ExtensionBundleVersionEntity versionEntity = 
metadataService.getExtensionBundleVersion("eb1", "1.0.0");
+        assertNotNull(versionEntity);
+
+        final List<ExtensionBundleVersionDependencyEntity> dependencies = 
metadataService.getDependenciesForBundleVersion(versionEntity.getId());
+        assertNotNull(dependencies);
+        assertEquals(1, dependencies.size());
+
+        final ExtensionBundleVersionDependencyEntity dependencyEntity = new 
ExtensionBundleVersionDependencyEntity();
+        dependencyEntity.setId(UUID.randomUUID().toString());
+        dependencyEntity.setExtensionBundleVersionId(versionEntity.getId());
+        dependencyEntity.setGroupId("com.foo");
+        dependencyEntity.setArtifactId("foo-nar");
+        dependencyEntity.setVersion("1.1.1");
+
+        metadataService.createDependency(dependencyEntity);
+
+        final List<ExtensionBundleVersionDependencyEntity> dependencies2 = 
metadataService.getDependenciesForBundleVersion(versionEntity.getId());
+        assertNotNull(dependencies2);
+        assertEquals(2, dependencies2.size());
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionDependencies() {
+        final List<ExtensionBundleVersionDependencyEntity> dependencies = 
metadataService.getDependenciesForBundleVersion("eb1-v1");
+        assertNotNull(dependencies);
+        assertEquals(1, dependencies.size());
+
+        final ExtensionBundleVersionDependencyEntity dependency = 
dependencies.get(0);
+        assertEquals("eb1-v1-dep1", dependency.getId());
+        assertEquals("eb1-v1", dependency.getExtensionBundleVersionId());
+        assertEquals("org.apache.nifi", dependency.getGroupId());
+        assertEquals("nifi-example-service-api-nar", 
dependency.getArtifactId());
+        assertEquals("2.0.0", dependency.getVersion());
+    }
+
+    @Test
+    public void testGetExtensionBundleVersionDependenciesWhenNoneExist() {
+        final List<ExtensionBundleVersionDependencyEntity> dependencies = 
metadataService.getDependenciesForBundleVersion("DOES-NOT-EXIST");
+        assertNotNull(dependencies);
+        assertEquals(0, dependencies.size());
+    }
+
+    //----------------- Extensions ---------------------------------
+
+    @Test
+    public void testCreateExtension() {
+        final ExtensionEntity extension = new ExtensionEntity();
+        extension.setId("4");
+        extension.setExtensionBundleVersionId("eb1-v1");
+        extension.setType("com.example.FooBarProcessor");
+        extension.setTypeDescription("This the FoorBarProcessor");
+        extension.setCategory(ExtensionEntityCategory.PROCESSOR);
+        extension.setRestricted(false);
+        extension.setTags("tag1, tag2");
+
+        metadataService.createExtension(extension);
+
+        final ExtensionEntity retrievedExtension = 
metadataService.getExtensionById(extension.getId());
+        assertEquals(extension.getId(), retrievedExtension.getId());
+        assertEquals(extension.getExtensionBundleVersionId(), 
retrievedExtension.getExtensionBundleVersionId());
+        assertEquals(extension.getType(), retrievedExtension.getType());
+        assertEquals(extension.getTypeDescription(), 
retrievedExtension.getTypeDescription());
+        assertEquals(extension.getCategory(), 
retrievedExtension.getCategory());
+        assertEquals(extension.isRestricted(), 
retrievedExtension.isRestricted());
+        assertEquals(extension.getTags(), retrievedExtension.getTags());
+
+        final List<ExtensionEntity> tag1Extensions = 
metadataService.getExtensionsByTag("tag1");
+        assertNotNull(tag1Extensions);
+        assertEquals(1, tag1Extensions.size());
+        assertEquals(extension.getId(), tag1Extensions.get(0).getId());
+
+        final List<ExtensionEntity> tag2Extensions = 
metadataService.getExtensionsByTag("tag2");
+        assertNotNull(tag2Extensions);
+        assertEquals(1, tag2Extensions.size());
+        assertEquals(extension.getId(), tag2Extensions.get(0).getId());
+    }
+
+    @Test
+    public void testGetExtensionById() {
+        final ExtensionEntity extension = 
metadataService.getExtensionById("e1");
+        assertNotNull(extension);
+        assertEquals("e1", extension.getId());
+        assertEquals("org.apache.nifi.ExampleProcessor", extension.getType());
+    }
+
+    @Test
+    public void testGetExtensionByIdDoesNotExist() {
+        final ExtensionEntity extension = 
metadataService.getExtensionById("does-not-exist");
+        assertNull(extension);
+    }
+
+    @Test
+    public void testGetAllExtensions() {
+        final List<ExtensionEntity> extensions = 
metadataService.getAllExtensions();
+        assertNotNull(extensions);
+        assertEquals(3, extensions.size());
+    }
+
+    @Test
+    public void testGetExtensionsByBundleVersionId() {
+        final List<ExtensionEntity> extensions = 
metadataService.getExtensionsByBundleVersionId("eb1-v1");
+        assertNotNull(extensions);
+        assertEquals(2, extensions.size());
+    }
+
+    @Test
+    public void testGetExtensionsByBundleVersionIdDoesNotExist() {
+        final List<ExtensionEntity> extensions = 
metadataService.getExtensionsByBundleVersionId("does-not-exist");
+        assertNotNull(extensions);
+        assertEquals(0, extensions.size());
+    }
+
+    @Test
+    public void testGetExtensionsByBundleCoordinate() {
+        final String bucketId = "3";
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "nifi-example-processors-nar";
+        final String version = "1.0.0";
+
+        final List<ExtensionEntity> extensions = 
metadataService.getExtensionsByBundleCoordinate(bucketId, groupId, artifactId, 
version);
+        assertNotNull(extensions);
+        assertEquals(2, extensions.size());
+    }
+
+    @Test
+    public void testGetExtensionsByBundleCoordinateDoesNotExist() {
+        final String bucketId = "3";
+        final String groupId = "org.apache.nifi";
+        final String artifactId = "does-not-exist";
+        final String version = "1.0.0";
+
+        final List<ExtensionEntity> extensions = 
metadataService.getExtensionsByBundleCoordinate(bucketId, groupId, artifactId, 
version);
+        assertNotNull(extensions);
+        assertEquals(0, extensions.size());
+    }
+
+    @Test
+    public void testGetExtensionsByCategory() {
+        final List<ExtensionEntity> services = 
metadataService.getExtensionsByCategory(ExtensionEntityCategory.CONTROLLER_SERVICE);
+        assertNotNull(services);
+        assertEquals(1, services.size());
+
+        final List<ExtensionEntity> processors = 
metadataService.getExtensionsByCategory(ExtensionEntityCategory.PROCESSOR);
+        assertNotNull(processors);
+        assertEquals(2, processors.size());
+    }
+
+    @Test
+    public void testGetExtensionTags() {
+        final Set<String> tags = metadataService.getAllExtensionTags();
+        assertNotNull(tags);
+        assertEquals(4, tags.size());
+    }
+
+    @Test
+    public void testDeleteExtension() {
+        final ExtensionEntity extension = 
metadataService.getExtensionById("e1");
+        assertNotNull(extension);
+
+        metadataService.deleteExtension(extension);
+
+        final ExtensionEntity deletedExtension = 
metadataService.getExtensionById("e1");
+        assertNull(deletedExtension);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
index df37e8e..116ba35 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/db/migration/TestLegacyDatabaseService.java
@@ -52,9 +52,11 @@ public class TestLegacyDatabaseService {
 
         jdbcTemplate = new JdbcTemplate(dataSource);
 
-        flyway = new Flyway();
-        flyway.setDataSource(dataSource);
-        flyway.setLocations("db/original");
+        flyway = Flyway.configure()
+                .dataSource(dataSource)
+                .locations("db/original")
+                .load();
+
         flyway.migrate();
 
         bucketEntityV1 = new BucketEntityV1();

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/event/TestEventFactory.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/event/TestEventFactory.java
 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/event/TestEventFactory.java
index a9ad911..e5075c0 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/event/TestEventFactory.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/event/TestEventFactory.java
@@ -17,6 +17,10 @@
 package org.apache.nifi.registry.event;
 
 import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.extension.ExtensionBundle;
+import org.apache.nifi.registry.extension.ExtensionBundleType;
+import org.apache.nifi.registry.extension.ExtensionBundleVersion;
+import org.apache.nifi.registry.extension.ExtensionBundleVersionMetadata;
 import org.apache.nifi.registry.flow.VersionedFlow;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
@@ -36,6 +40,8 @@ public class TestEventFactory {
     private Bucket bucket;
     private VersionedFlow versionedFlow;
     private VersionedFlowSnapshot versionedFlowSnapshot;
+    private ExtensionBundle extensionBundle;
+    private ExtensionBundleVersion extensionBundleVersion;
 
     @Before
     public void setup() {
@@ -60,6 +66,24 @@ public class TestEventFactory {
         versionedFlowSnapshot = new VersionedFlowSnapshot();
         versionedFlowSnapshot.setSnapshotMetadata(metadata);
         versionedFlowSnapshot.setFlowContents(new VersionedProcessGroup());
+
+        extensionBundle = new ExtensionBundle();
+        extensionBundle.setIdentifier(UUID.randomUUID().toString());
+        extensionBundle.setBucketIdentifier(bucket.getIdentifier());
+        extensionBundle.setBundleType(ExtensionBundleType.NIFI_NAR);
+        extensionBundle.setGroupId("org.apache.nifi");
+        extensionBundle.setArtifactId("nifi-foo-nar");
+
+        final ExtensionBundleVersionMetadata bundleVersionMetadata = new 
ExtensionBundleVersionMetadata();
+        bundleVersionMetadata.setId(UUID.randomUUID().toString());
+        bundleVersionMetadata.setVersion("1.0.0");
+        bundleVersionMetadata.setBucketId(bucket.getIdentifier());
+        
bundleVersionMetadata.setExtensionBundleId(extensionBundle.getIdentifier());
+
+        extensionBundleVersion = new ExtensionBundleVersion();
+        extensionBundleVersion.setVersionMetadata(bundleVersionMetadata);
+        extensionBundleVersion.setExtensionBundle(extensionBundle);
+        extensionBundleVersion.setBucket(bucket);
     }
 
     @Test
@@ -166,4 +190,57 @@ public class TestEventFactory {
         assertEquals("", event.getField(EventFieldName.COMMENT).getValue());
     }
 
+    @Test
+    public void testExtensionBundleCreated() {
+        final Event event = 
EventFactory.extensionBundleCreated(extensionBundle);
+        event.validate();
+
+        assertEquals(EventType.CREATE_EXTENSION_BUNDLE, event.getEventType());
+        assertEquals(3, event.getFields().size());
+
+        assertEquals(bucket.getIdentifier(), 
event.getField(EventFieldName.BUCKET_ID).getValue());
+        assertEquals(extensionBundle.getIdentifier(), 
event.getField(EventFieldName.EXTENSION_BUNDLE_ID).getValue());
+        assertEquals("unknown", 
event.getField(EventFieldName.USER).getValue());
+    }
+
+    @Test
+    public void testExtensionBundleDeleted() {
+        final Event event = 
EventFactory.extensionBundleDeleted(extensionBundle);
+        event.validate();
+
+        assertEquals(EventType.DELETE_EXTENSION_BUNDLE, event.getEventType());
+        assertEquals(3, event.getFields().size());
+
+        assertEquals(bucket.getIdentifier(), 
event.getField(EventFieldName.BUCKET_ID).getValue());
+        assertEquals(extensionBundle.getIdentifier(), 
event.getField(EventFieldName.EXTENSION_BUNDLE_ID).getValue());
+        assertEquals("unknown", 
event.getField(EventFieldName.USER).getValue());
+    }
+
+    @Test
+    public void testExtensionBundleVersionCreated() {
+        final Event event = 
EventFactory.extensionBundleVersionCreated(extensionBundleVersion);
+        event.validate();
+
+        assertEquals(EventType.CREATE_EXTENSION_BUNDLE_VERSION, 
event.getEventType());
+        assertEquals(4, event.getFields().size());
+
+        assertEquals(bucket.getIdentifier(), 
event.getField(EventFieldName.BUCKET_ID).getValue());
+        assertEquals(extensionBundle.getIdentifier(), 
event.getField(EventFieldName.EXTENSION_BUNDLE_ID).getValue());
+        assertEquals(extensionBundleVersion.getVersionMetadata().getVersion(), 
event.getField(EventFieldName.VERSION).getValue());
+        assertEquals("unknown", 
event.getField(EventFieldName.USER).getValue());
+    }
+
+    @Test
+    public void testExtensionBundleVersionDeleted() {
+        final Event event = 
EventFactory.extensionBundleVersionDeleted(extensionBundleVersion);
+        event.validate();
+
+        assertEquals(EventType.DELETE_EXTENSION_BUNDLE_VERSION, 
event.getEventType());
+        assertEquals(4, event.getFields().size());
+
+        assertEquals(bucket.getIdentifier(), 
event.getField(EventFieldName.BUCKET_ID).getValue());
+        assertEquals(extensionBundle.getIdentifier(), 
event.getField(EventFieldName.EXTENSION_BUNDLE_ID).getValue());
+        assertEquals(extensionBundleVersion.getVersionMetadata().getVersion(), 
event.getField(EventFieldName.VERSION).getValue());
+        assertEquals("unknown", 
event.getField(EventFieldName.USER).getValue());
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/MockExtensionBundlePersistenceProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/MockExtensionBundlePersistenceProvider.java
 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/MockExtensionBundlePersistenceProvider.java
new file mode 100644
index 0000000..ba7f12f
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/MockExtensionBundlePersistenceProvider.java
@@ -0,0 +1,62 @@
+/*
+ * 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.provider;
+
+import org.apache.nifi.registry.extension.ExtensionBundleContext;
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceException;
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+public class MockExtensionBundlePersistenceProvider implements 
ExtensionBundlePersistenceProvider {
+
+    private Map<String,String> properties;
+
+    @Override
+    public void saveBundleVersion(ExtensionBundleContext context, InputStream 
contentStream)
+            throws ExtensionBundlePersistenceException {
+
+    }
+
+    @Override
+    public void getBundleVersion(ExtensionBundleContext context, OutputStream 
outputStream) throws ExtensionBundlePersistenceException {
+
+    }
+
+    @Override
+    public void deleteBundleVersion(ExtensionBundleContext context) throws 
ExtensionBundlePersistenceException {
+
+    }
+
+    @Override
+    public void deleteAllBundleVersions(String bucketId, String bucketName, 
String groupId, String artifactId) throws ExtensionBundlePersistenceException {
+
+    }
+
+    @Override
+    public void onConfigured(ProviderConfigurationContext configurationContext)
+            throws ProviderCreationException {
+        properties = configurationContext.getProperties();
+    }
+
+    public Map<String,String> getProperties() {
+        return properties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/f1e5aef7/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java
 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java
index 30f66ef..2105cff 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/test/java/org/apache/nifi/registry/provider/TestStandardProviderFactory.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.registry.provider;
 
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider;
 import org.apache.nifi.registry.extension.ExtensionManager;
 import org.apache.nifi.registry.flow.FlowPersistenceProvider;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
@@ -47,6 +48,14 @@ public class TestStandardProviderFactory {
         assertNotNull(mockFlowProvider.getProperties());
         assertEquals("flow foo", mockFlowProvider.getProperties().get("Flow 
Property 1"));
         assertEquals("flow bar", mockFlowProvider.getProperties().get("Flow 
Property 2"));
+
+        final ExtensionBundlePersistenceProvider bundlePersistenceProvider = 
providerFactory.getExtensionBundlePersistenceProvider();
+        assertNotNull(bundlePersistenceProvider);
+
+        final MockExtensionBundlePersistenceProvider 
mockBundlePersistenceProvider = (MockExtensionBundlePersistenceProvider) 
bundlePersistenceProvider;
+        assertNotNull(mockBundlePersistenceProvider.getProperties());
+        assertEquals("extension foo", 
mockBundlePersistenceProvider.getProperties().get("Extension Property 1"));
+        assertEquals("extension bar", 
mockBundlePersistenceProvider.getProperties().get("Extension Property 2"));
     }
 
     @Test(expected = ProviderFactoryException.class)

Reply via email to