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/db/entity/ExtensionEntity.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionEntity.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionEntity.java
new file mode 100644
index 0000000..c48ac9f
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionEntity.java
@@ -0,0 +1,92 @@
+/*
+ * 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 ExtensionEntity {
+
+    private String id;
+
+    private String extensionBundleVersionId;
+
+    private String type;
+
+    private String typeDescription;
+
+    private boolean restricted;
+
+    private ExtensionEntityCategory category;
+
+    // Comma separated list of tags so we don't have to query tag table for 
each extension
+    private String tags;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getExtensionBundleVersionId() {
+        return extensionBundleVersionId;
+    }
+
+    public void setExtensionBundleVersionId(String extensionBundleVersionId) {
+        this.extensionBundleVersionId = extensionBundleVersionId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getTypeDescription() {
+        return typeDescription;
+    }
+
+    public void setTypeDescription(String typeDescription) {
+        this.typeDescription = typeDescription;
+    }
+
+    public boolean isRestricted() {
+        return restricted;
+    }
+
+    public void setRestricted(boolean restricted) {
+        this.restricted = restricted;
+    }
+
+    public ExtensionEntityCategory getCategory() {
+        return category;
+    }
+
+    public void setCategory(ExtensionEntityCategory category) {
+        this.category = category;
+    }
+
+    public String getTags() {
+        return tags;
+    }
+
+    public void setTags(String tags) {
+        this.tags = tags;
+    }
+
+}

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/db/entity/ExtensionEntityCategory.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionEntityCategory.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionEntityCategory.java
new file mode 100644
index 0000000..b072b4a
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionEntityCategory.java
@@ -0,0 +1,27 @@
+/*
+ * 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 enum ExtensionEntityCategory {
+
+    PROCESSOR,
+
+    CONTROLLER_SERVICE,
+
+    REPORTING_TASK;
+
+}

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/db/entity/ExtensionTagEntity.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionTagEntity.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionTagEntity.java
new file mode 100644
index 0000000..1612442
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/entity/ExtensionTagEntity.java
@@ -0,0 +1,41 @@
+/*
+ * 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 ExtensionTagEntity {
+
+    private String extensionId;
+
+    private String tag;
+
+    public String getExtensionId() {
+        return extensionId;
+    }
+
+    public void setExtensionId(String extensionId) {
+        this.extensionId = extensionId;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
+
+}

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/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
index 7b3df05..82c0a4c 100644
--- 
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
@@ -18,6 +18,8 @@ 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.ExtensionBundleEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntityType;
 import org.apache.nifi.registry.db.entity.FlowEntity;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.lang.Nullable;
@@ -38,6 +40,13 @@ public class BucketItemEntityRowMapper implements 
RowMapper<BucketItemEntity> {
             case FLOW:
                 item = new FlowEntity();
                 break;
+            case EXTENSION_BUNDLE:
+                final ExtensionBundleEntity bundleEntity = new 
ExtensionBundleEntity();
+                
bundleEntity.setBundleType(ExtensionBundleEntityType.valueOf(rs.getString("BUNDLE_TYPE")));
+                bundleEntity.setGroupId(rs.getString("BUNDLE_GROUP_ID"));
+                bundleEntity.setArtifactId(rs.getString("BUNDLE_ARTIFACT_ID"));
+                item = bundleEntity;
+                break;
             default:
                 // should never happen
                 item = new BucketItemEntity();

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/db/mapper/ExtensionBundleEntityRowMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleEntityRowMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleEntityRowMapper.java
new file mode 100644
index 0000000..6375411
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleEntityRowMapper.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ExtensionBundleEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntityType;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ExtensionBundleEntityRowMapper implements 
RowMapper<ExtensionBundleEntity> {
+
+    @Override
+    public ExtensionBundleEntity mapRow(final ResultSet rs, final int i) 
throws SQLException {
+        final ExtensionBundleEntity entity = new ExtensionBundleEntity();
+
+        // BucketItemEntity fields
+        entity.setId(rs.getString("ID"));
+        entity.setName(rs.getString("NAME"));
+        entity.setDescription(rs.getString("DESCRIPTION"));
+        entity.setCreated(rs.getTimestamp("CREATED"));
+        entity.setModified(rs.getTimestamp("MODIFIED"));
+        entity.setBucketId(rs.getString("BUCKET_ID"));
+        entity.setType(BucketItemEntityType.EXTENSION_BUNDLE);
+
+        // ExtensionBundleEntity fields
+        
entity.setBundleType(ExtensionBundleEntityType.valueOf(rs.getString("BUNDLE_TYPE")));
+        entity.setGroupId(rs.getString("GROUP_ID"));
+        entity.setArtifactId(rs.getString("ARTIFACT_ID"));
+
+        return entity;
+    }
+
+}

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/db/mapper/ExtensionBundleEntityWithBucketNameRowMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleEntityWithBucketNameRowMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleEntityWithBucketNameRowMapper.java
new file mode 100644
index 0000000..3fb22b6
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleEntityWithBucketNameRowMapper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ExtensionBundleEntity;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ExtensionBundleEntityWithBucketNameRowMapper extends 
ExtensionBundleEntityRowMapper {
+
+    @Override
+    public ExtensionBundleEntity mapRow(final ResultSet rs, final int i) 
throws SQLException {
+        final ExtensionBundleEntity entity = super.mapRow(rs, i);
+        entity.setBucketName(rs.getString("BUCKET_NAME"));
+        return entity;
+    }
+
+}

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/db/mapper/ExtensionBundleVersionDependencyEntityRowMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleVersionDependencyEntityRowMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleVersionDependencyEntityRowMapper.java
new file mode 100644
index 0000000..044e245
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleVersionDependencyEntityRowMapper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ExtensionBundleVersionDependencyEntity;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ExtensionBundleVersionDependencyEntityRowMapper implements 
RowMapper<ExtensionBundleVersionDependencyEntity> {
+
+    @Override
+    public ExtensionBundleVersionDependencyEntity mapRow(final ResultSet rs, 
final int i) throws SQLException {
+        final ExtensionBundleVersionDependencyEntity entity = new 
ExtensionBundleVersionDependencyEntity();
+        entity.setId(rs.getString("ID"));
+        
entity.setExtensionBundleVersionId(rs.getString("EXTENSION_BUNDLE_VERSION_ID"));
+        entity.setGroupId(rs.getString("GROUP_ID"));
+        entity.setArtifactId(rs.getString("ARTIFACT_ID"));
+        entity.setVersion(rs.getString("VERSION"));
+        return entity;
+    }
+
+}

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/db/mapper/ExtensionBundleVersionEntityRowMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleVersionEntityRowMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleVersionEntityRowMapper.java
new file mode 100644
index 0000000..60ca48f
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionBundleVersionEntityRowMapper.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.ExtensionBundleVersionEntity;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ExtensionBundleVersionEntityRowMapper implements 
RowMapper<ExtensionBundleVersionEntity> {
+
+    @Override
+    public ExtensionBundleVersionEntity mapRow(final ResultSet rs, final int 
i) throws SQLException {
+        final ExtensionBundleVersionEntity entity = new 
ExtensionBundleVersionEntity();
+        entity.setId(rs.getString("ID"));
+        entity.setExtensionBundleId(rs.getString("EXTENSION_BUNDLE_ID"));
+        entity.setVersion(rs.getString("VERSION"));
+        entity.setSha256Hex(rs.getString("SHA_256_HEX"));
+        entity.setSha256Supplied(rs.getInt("SHA_256_SUPPLIED") == 1);
+
+        entity.setCreated(rs.getTimestamp("CREATED"));
+        entity.setCreatedBy(rs.getString("CREATED_BY"));
+        entity.setDescription(rs.getString("DESCRIPTION"));
+
+        return entity;
+    }
+
+}

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/db/mapper/ExtensionEntityRowMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionEntityRowMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionEntityRowMapper.java
new file mode 100644
index 0000000..057fbdb
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionEntityRowMapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ExtensionEntity;
+import org.apache.nifi.registry.db.entity.ExtensionEntityCategory;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ExtensionEntityRowMapper implements RowMapper<ExtensionEntity> {
+
+    @Override
+    public ExtensionEntity mapRow(ResultSet rs, int i) throws SQLException {
+        final ExtensionEntity entity = new ExtensionEntity();
+        entity.setId(rs.getString("ID"));
+        
entity.setExtensionBundleVersionId(rs.getString("EXTENSION_BUNDLE_VERSION_ID"));
+        entity.setType(rs.getString("TYPE"));
+        entity.setTypeDescription(rs.getString("TYPE_DESCRIPTION"));
+        entity.setRestricted(rs.getInt("IS_RESTRICTED") == 1);
+        
entity.setCategory(ExtensionEntityCategory.valueOf(rs.getString("CATEGORY")));
+        entity.setTags(rs.getString("TAGS"));
+        return entity;
+    }
+
+}

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/db/mapper/ExtensionTagEntityRowMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionTagEntityRowMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionTagEntityRowMapper.java
new file mode 100644
index 0000000..a3be127
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/db/mapper/ExtensionTagEntityRowMapper.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ExtensionTagEntity;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ExtensionTagEntityRowMapper implements 
RowMapper<ExtensionTagEntity> {
+
+    @Override
+    public ExtensionTagEntity mapRow(final ResultSet rs, final int i) throws 
SQLException {
+        final ExtensionTagEntity entity = new ExtensionTagEntity();
+        entity.setExtensionId(rs.getString("EXTENSION_ID"));
+        entity.setTag(rs.getString("TAG"));
+        return entity;
+    }
+
+}

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/event/EventFactory.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/event/EventFactory.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/event/EventFactory.java
index b837d6d..5fc885b 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/event/EventFactory.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/event/EventFactory.java
@@ -17,6 +17,8 @@
 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.ExtensionBundleVersion;
 import org.apache.nifi.registry.flow.VersionedFlow;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.registry.hook.Event;
@@ -94,4 +96,41 @@ public class EventFactory {
                 .build();
     }
 
+    public static Event extensionBundleCreated(final ExtensionBundle bundle) {
+        return new StandardEvent.Builder()
+                .eventType(EventType.CREATE_EXTENSION_BUNDLE)
+                .addField(EventFieldName.BUCKET_ID, 
bundle.getBucketIdentifier())
+                .addField(EventFieldName.EXTENSION_BUNDLE_ID, 
bundle.getIdentifier())
+                .addField(EventFieldName.USER, 
NiFiUserUtils.getNiFiUserIdentity())
+                .build();
+    }
+
+    public static Event extensionBundleDeleted(final ExtensionBundle bundle) {
+        return new StandardEvent.Builder()
+                .eventType(EventType.DELETE_EXTENSION_BUNDLE)
+                .addField(EventFieldName.BUCKET_ID, 
bundle.getBucketIdentifier())
+                .addField(EventFieldName.EXTENSION_BUNDLE_ID, 
bundle.getIdentifier())
+                .addField(EventFieldName.USER, 
NiFiUserUtils.getNiFiUserIdentity())
+                .build();
+    }
+
+    public static Event extensionBundleVersionCreated(final 
ExtensionBundleVersion bundleVersion) {
+        return new StandardEvent.Builder()
+                .eventType(EventType.CREATE_EXTENSION_BUNDLE_VERSION)
+                .addField(EventFieldName.BUCKET_ID, 
bundleVersion.getVersionMetadata().getBucketId())
+                .addField(EventFieldName.EXTENSION_BUNDLE_ID, 
bundleVersion.getVersionMetadata().getExtensionBundleId())
+                .addField(EventFieldName.VERSION, 
String.valueOf(bundleVersion.getVersionMetadata().getVersion()))
+                .addField(EventFieldName.USER, 
NiFiUserUtils.getNiFiUserIdentity())
+                .build();
+    }
+
+    public static Event extensionBundleVersionDeleted(final 
ExtensionBundleVersion bundleVersion) {
+        return new StandardEvent.Builder()
+                .eventType(EventType.DELETE_EXTENSION_BUNDLE_VERSION)
+                .addField(EventFieldName.BUCKET_ID, 
bundleVersion.getVersionMetadata().getBucketId())
+                .addField(EventFieldName.EXTENSION_BUNDLE_ID, 
bundleVersion.getVersionMetadata().getExtensionBundleId())
+                .addField(EventFieldName.VERSION, 
String.valueOf(bundleVersion.getVersionMetadata().getVersion()))
+                .addField(EventFieldName.USER, 
NiFiUserUtils.getNiFiUserIdentity())
+                .build();
+    }
 }

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/extension/ExtensionManager.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionManager.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionManager.java
index ca3259d..16e0e93 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionManager.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionManager.java
@@ -58,6 +58,7 @@ public class ExtensionManager {
         classes.add(Authorizer.class);
         classes.add(IdentityProvider.class);
         classes.add(EventHookProvider.class);
+        classes.add(ExtensionBundlePersistenceProvider.class);
         EXTENSION_CLASSES = Collections.unmodifiableList(classes);
     }
 

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/provider/ProviderFactory.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/ProviderFactory.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/ProviderFactory.java
index a3f3276..450868f 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/ProviderFactory.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/ProviderFactory.java
@@ -18,6 +18,7 @@ package org.apache.nifi.registry.provider;
 
 import java.util.List;
 
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider;
 import org.apache.nifi.registry.flow.FlowPersistenceProvider;
 import org.apache.nifi.registry.hook.EventHookProvider;
 
@@ -43,4 +44,9 @@ public interface ProviderFactory {
      */
     List<EventHookProvider> getEventHookProviders();
 
+    /**
+     * @return the configured ExtensionBundlePersistenceProvider
+     */
+    ExtensionBundlePersistenceProvider getExtensionBundlePersistenceProvider();
+
 }

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/provider/StandardProviderFactory.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/StandardProviderFactory.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/StandardProviderFactory.java
index 65ba914..89b2586 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/StandardProviderFactory.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/StandardProviderFactory.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.hook.EventHookProvider;
@@ -75,6 +76,7 @@ public class StandardProviderFactory implements 
ProviderFactory {
 
     private FlowPersistenceProvider flowPersistenceProvider;
     private List<EventHookProvider> eventHookProviders;
+    private ExtensionBundlePersistenceProvider 
extensionBundlePersistenceProvider;
 
     @Autowired
     public StandardProviderFactory(final NiFiRegistryProperties properties, 
final ExtensionManager extensionManager) {
@@ -204,6 +206,45 @@ public class StandardProviderFactory implements 
ProviderFactory {
         return eventHookProviders;
     }
 
+    @Bean
+    @Override
+    public synchronized ExtensionBundlePersistenceProvider 
getExtensionBundlePersistenceProvider() {
+        if (extensionBundlePersistenceProvider == null) {
+            if (providersHolder.get() == null) {
+                throw new ProviderFactoryException("ProviderFactory must be 
initialized before obtaining a Provider");
+            }
+
+            final Providers providers = providersHolder.get();
+            final org.apache.nifi.registry.provider.generated.Provider 
jaxbExtensionBundleProvider = providers.getExtensionBundlePersistenceProvider();
+            final String extensionBundleProviderClassName = 
jaxbExtensionBundleProvider.getClazz();
+
+            try {
+                final ClassLoader classLoader = 
extensionManager.getExtensionClassLoader(extensionBundleProviderClassName);
+                if (classLoader == null) {
+                    throw new IllegalStateException("Extension not found in 
any of the configured class loaders: " + extensionBundleProviderClassName);
+                }
+
+                final Class<?> rawProviderClass = 
Class.forName(extensionBundleProviderClassName, true, classLoader);
+
+                final Class<? extends ExtensionBundlePersistenceProvider> 
extensionBundleProviderClass =
+                        
rawProviderClass.asSubclass(ExtensionBundlePersistenceProvider.class);
+
+                final Constructor constructor = 
extensionBundleProviderClass.getConstructor();
+                extensionBundlePersistenceProvider = 
(ExtensionBundlePersistenceProvider) constructor.newInstance();
+
+                LOGGER.info("Instantiated ExtensionBundlePersistenceProvider 
with class name {}", new Object[] {extensionBundleProviderClassName});
+            } catch (Exception e) {
+                throw new ProviderFactoryException("Error creating 
ExtensionBundlePersistenceProvider with class name: " + 
extensionBundleProviderClassName, e);
+            }
+
+            final ProviderConfigurationContext configurationContext = 
createConfigurationContext(jaxbExtensionBundleProvider.getProperty());
+            
extensionBundlePersistenceProvider.onConfigured(configurationContext);
+            LOGGER.info("Configured FlowPersistenceProvider with class name 
{}", new Object[] {extensionBundleProviderClassName});
+        }
+
+        return extensionBundlePersistenceProvider;
+    }
+
     private ProviderConfigurationContext createConfigurationContext(final 
List<Property> configProperties) {
         final Map<String,String> properties = new HashMap<>();
 

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/provider/extension/FileSystemExtensionBundlePersistenceProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/extension/FileSystemExtensionBundlePersistenceProvider.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/extension/FileSystemExtensionBundlePersistenceProvider.java
new file mode 100644
index 0000000..1d8c9cc
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/extension/FileSystemExtensionBundlePersistenceProvider.java
@@ -0,0 +1,231 @@
+/*
+ * 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.extension;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.extension.ExtensionBundleContext;
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceException;
+import org.apache.nifi.registry.extension.ExtensionBundlePersistenceProvider;
+import org.apache.nifi.registry.flow.FlowPersistenceException;
+import org.apache.nifi.registry.provider.ProviderConfigurationContext;
+import org.apache.nifi.registry.provider.ProviderCreationException;
+import org.apache.nifi.registry.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+/**
+ * An {@link ExtensionBundlePersistenceProvider} that uses local file-system 
for storage.
+ */
+public class FileSystemExtensionBundlePersistenceProvider implements 
ExtensionBundlePersistenceProvider {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(FileSystemExtensionBundlePersistenceProvider.class);
+
+    static final String BUNDLE_STORAGE_DIR_PROP = "Extension Bundle Storage 
Directory";
+
+    static final String NAR_EXTENSION = ".nar";
+    static final String CPP_EXTENSION = ".cpp";
+
+    private File bundleStorageDir;
+
+    @Override
+    public void onConfigured(final ProviderConfigurationContext 
configurationContext)
+            throws ProviderCreationException {
+        final Map<String,String> props = configurationContext.getProperties();
+        if (!props.containsKey(BUNDLE_STORAGE_DIR_PROP)) {
+            throw new ProviderCreationException("The property " + 
BUNDLE_STORAGE_DIR_PROP + " must be provided");
+        }
+
+        final String bundleStorageDirValue = 
props.get(BUNDLE_STORAGE_DIR_PROP);
+        if (StringUtils.isBlank(bundleStorageDirValue)) {
+            throw new ProviderCreationException("The property " + 
BUNDLE_STORAGE_DIR_PROP + " cannot be null or blank");
+        }
+
+        try {
+            bundleStorageDir = new File(bundleStorageDirValue);
+            FileUtils.ensureDirectoryExistAndCanReadAndWrite(bundleStorageDir);
+            LOGGER.info("Configured ExtensionBundlePersistenceProvider with 
Extension Bundle Storage Directory {}",
+                    new Object[] {bundleStorageDir.getAbsolutePath()});
+        } catch (IOException e) {
+            throw new ProviderCreationException(e);
+        }
+    }
+
+    @Override
+    public synchronized void saveBundleVersion(final ExtensionBundleContext 
context, final InputStream contentStream)
+            throws ExtensionBundlePersistenceException {
+
+        final File bundleVersionDir = 
getBundleVersionDirectory(bundleStorageDir, context.getBucketName(),
+                context.getBundleGroupId(), context.getBundleArtifactId(), 
context.getBundleVersion());
+        try {
+            FileUtils.ensureDirectoryExistAndCanReadAndWrite(bundleVersionDir);
+        } catch (IOException e) {
+            throw new FlowPersistenceException("Error accessing directory for 
extension bundle version at "
+                    + bundleVersionDir.getAbsolutePath(), e);
+        }
+
+        final File bundleFile = getBundleFile(bundleVersionDir, 
context.getBundleArtifactId(),
+                context.getBundleVersion(), context.getBundleType());
+
+        if (bundleFile.exists()) {
+            throw new ExtensionBundlePersistenceException("Unable to save 
because an extension bundle already exists at "
+                    + bundleFile.getAbsolutePath());
+        }
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Writing extension bundle to {}", new 
Object[]{bundleFile.getAbsolutePath()});
+        }
+
+        try (final OutputStream out = new FileOutputStream(bundleFile)) {
+            IOUtils.copy(contentStream, out);
+            out.flush();
+        } catch (Exception e) {
+            throw new FlowPersistenceException("Unable to write bundle file to 
disk due to " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public synchronized void getBundleVersion(final ExtensionBundleContext 
context, final OutputStream outputStream)
+            throws ExtensionBundlePersistenceException {
+
+        final File bundleVersionDir = 
getBundleVersionDirectory(bundleStorageDir, context.getBucketName(),
+                context.getBundleGroupId(), context.getBundleArtifactId(), 
context.getBundleVersion());
+
+        final File bundleFile = getBundleFile(bundleVersionDir, 
context.getBundleArtifactId(),
+                context.getBundleVersion(), context.getBundleType());
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Reading extension bundle from {}", new 
Object[]{bundleFile.getAbsolutePath()});
+        }
+
+        try (final InputStream in = new FileInputStream(bundleFile);
+             final BufferedInputStream bufIn = new BufferedInputStream(in)) {
+            IOUtils.copy(bufIn, outputStream);
+            outputStream.flush();
+        } catch (FileNotFoundException e) {
+            throw new ExtensionBundlePersistenceException("Extension bundle 
content was not found for: " + bundleFile.getAbsolutePath(), e);
+        } catch (IOException e) {
+            throw new ExtensionBundlePersistenceException("Error reading 
extension bundle content", e);
+        }
+    }
+
+    @Override
+    public synchronized void deleteBundleVersion(final ExtensionBundleContext 
context) throws ExtensionBundlePersistenceException {
+        final File bundleVersionDir = 
getBundleVersionDirectory(bundleStorageDir, context.getBucketName(),
+                context.getBundleGroupId(), context.getBundleArtifactId(), 
context.getBundleVersion());
+
+        final File bundleFile = getBundleFile(bundleVersionDir, 
context.getBundleArtifactId(),
+                context.getBundleVersion(), context.getBundleType());
+
+        if (!bundleFile.exists()) {
+            LOGGER.warn("Extension bundle content does not exist at {}", new 
Object[] {bundleFile.getAbsolutePath()});
+            return;
+        }
+
+        final boolean deleted = bundleFile.delete();
+        if (!deleted) {
+            throw new ExtensionBundlePersistenceException("Unable to delete 
extension bundle content at " + bundleFile.getAbsolutePath());
+        }
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Deleted extension bundle content at {}", new 
Object[] {bundleFile.getAbsolutePath()});
+        }
+    }
+
+    @Override
+    public synchronized void deleteAllBundleVersions(final String bucketId, 
final String bucketName, final String groupId, final String artifactId)
+            throws ExtensionBundlePersistenceException {
+
+        final File bundleDir = getBundleDirectory(bundleStorageDir, 
bucketName, groupId, artifactId);
+        if (!bundleDir.exists()) {
+            LOGGER.warn("Extension bundle directory does not exist at {}", new 
Object[] {bundleDir.getAbsolutePath()});
+            return;
+        }
+
+        // delete everything under the bundle directory
+        try {
+            org.apache.commons.io.FileUtils.cleanDirectory(bundleDir);
+        } catch (IOException e) {
+            throw new FlowPersistenceException("Error deleting extension 
bundles at " + bundleDir.getAbsolutePath(), e);
+        }
+
+        // delete the directory for the bundle
+        final boolean bundleDirDeleted = bundleDir.delete();
+        if (!bundleDirDeleted) {
+            LOGGER.error("Unable to delete extension bundle directory: " + 
bundleDir.getAbsolutePath());
+        }
+
+        // delete the directory for the group and bucket if there is nothing 
left
+        final File groupDir = bundleDir.getParentFile();
+        final File[] groupFiles = groupDir.listFiles();
+        if (groupFiles.length == 0) {
+            final boolean deletedGroup = groupDir.delete();
+            if (!deletedGroup) {
+                LOGGER.error("Unable to delete group directory: " + 
groupDir.getAbsolutePath());
+            } else {
+                final File bucketDir = groupDir.getParentFile();
+                final File[] bucketFiles = bucketDir.listFiles();
+                if (bucketFiles.length == 0){
+                    final boolean deletedBucket = bucketDir.delete();
+                    if (!deletedBucket) {
+                        LOGGER.error("Unable to delete bucket directory: " + 
bucketDir.getAbsolutePath());
+                    }
+                }
+            }
+        }
+    }
+
+    static File getBundleDirectory(final File bundleStorageDir, final String 
bucketName, final String groupId, final String artifactId) {
+        return new File(bundleStorageDir, sanitize(bucketName) + "/" + 
sanitize(groupId) + "/" + sanitize(artifactId));
+    }
+
+    static File getBundleVersionDirectory(final File bundleStorageDir, final 
String bucketName, final String groupId, final String artifactId, final String 
version) {
+        return new File(bundleStorageDir, sanitize(bucketName) + "/" + 
sanitize(groupId) + "/" + sanitize(artifactId) + "/" + sanitize(version));
+    }
+
+    static File getBundleFile(final File parentDir, final String artifactId, 
final String version, final ExtensionBundleContext.BundleType bundleType) {
+        final String bundleFileExtension = getBundleFileExtension(bundleType);
+        final String bundleFilename = sanitize(artifactId) + "-" + 
sanitize(version) + bundleFileExtension;
+        return new File(parentDir, bundleFilename);
+    }
+
+    static String sanitize(final String input) {
+        return FileUtils.sanitizeFilename(input).trim().toLowerCase();
+    }
+
+    static String getBundleFileExtension(final 
ExtensionBundleContext.BundleType bundleType) {
+        switch (bundleType) {
+            case NIFI_NAR:
+                return NAR_EXTENSION;
+            case MINIFI_CPP:
+                return CPP_EXTENSION;
+            default:
+                throw new IllegalArgumentException("Unknown bundle type: " + 
bundleType);
+        }
+    }
+
+}

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/provider/extension/StandardExtensionBundleContext.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/extension/StandardExtensionBundleContext.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/extension/StandardExtensionBundleContext.java
new file mode 100644
index 0000000..8b3e068
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/provider/extension/StandardExtensionBundleContext.java
@@ -0,0 +1,176 @@
+/*
+ * 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.extension;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.nifi.registry.extension.ExtensionBundleContext;
+
+public class StandardExtensionBundleContext implements ExtensionBundleContext {
+
+    private final BundleType bundleType;
+    private final String bucketId;
+    private final String bucketName;
+    private final String bundleId;
+    private final String bundleGroupId;
+    private final String bundleArtifactId;
+    private final String bundleVersion;
+    private final String description;
+    private final String author;
+    private final long timestamp;
+
+    private StandardExtensionBundleContext(final Builder builder) {
+        this.bundleType = builder.bundleType;
+        this.bucketId = builder.bucketId;
+        this.bucketName = builder.bucketName;
+        this.bundleId = builder.bundleId;
+        this.bundleGroupId = builder.bundleGroupId;
+        this.bundleArtifactId = builder.bundleArtifactId;
+        this.bundleVersion = builder.bundleVersion;
+        this.description = builder.description;
+        this.author = builder.author;
+        this.timestamp = builder.timestamp;
+        Validate.notNull(this.bundleType);
+        Validate.notBlank(this.bucketId);
+        Validate.notBlank(this.bucketName);
+        Validate.notBlank(this.bundleId);
+        Validate.notBlank(this.bundleGroupId);
+        Validate.notBlank(this.bundleArtifactId);
+        Validate.notBlank(this.bundleVersion);
+        Validate.notBlank(this.author);
+    }
+
+
+    @Override
+    public BundleType getBundleType() {
+        return bundleType;
+    }
+
+    @Override
+    public String getBucketId() {
+        return bucketId;
+    }
+
+    @Override
+    public String getBucketName() {
+        return bucketName;
+    }
+
+    @Override
+    public String getBundleId() {
+        return bundleId;
+    }
+
+    @Override
+    public String getBundleGroupId() {
+        return bundleGroupId;
+    }
+
+    @Override
+    public String getBundleArtifactId() {
+        return bundleArtifactId;
+    }
+
+    @Override
+    public String getBundleVersion() {
+        return bundleVersion;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public String getAuthor() {
+        return author;
+    }
+
+    public static class Builder {
+
+        private BundleType bundleType;
+        private String bucketId;
+        private String bucketName;
+        private String bundleId;
+        private String bundleGroupId;
+        private String bundleArtifactId;
+        private String bundleVersion;
+        private String description;
+        private String author;
+        private long timestamp;
+
+        public Builder bundleType(final BundleType bundleType) {
+            this.bundleType = bundleType;
+            return this;
+        }
+
+        public Builder bucketId(final String bucketId) {
+            this.bucketId = bucketId;
+            return this;
+        }
+
+        public Builder bucketName(final String bucketName) {
+            this.bucketName = bucketName;
+            return this;
+        }
+
+        public Builder bundleId(final String bundleId) {
+            this.bundleId = bundleId;
+            return this;
+        }
+
+        public Builder bundleGroupId(final String bundleGroupId) {
+            this.bundleGroupId = bundleGroupId;
+            return this;
+        }
+
+        public Builder bundleArtifactId(final String bundleArtifactId) {
+            this.bundleArtifactId = bundleArtifactId;
+            return this;
+        }
+
+        public Builder bundleVersion(final String bundleVersion) {
+            this.bundleVersion = bundleVersion;
+            return this;
+        }
+
+        public Builder description(final String description) {
+            this.description = description;
+            return this;
+        }
+
+        public Builder author(final String author) {
+            this.author = author;
+            return this;
+        }
+
+        public Builder timestamp(final long timestamp) {
+            this.timestamp = timestamp;
+            return this;
+        }
+
+        public StandardExtensionBundleContext build() {
+            return new StandardExtensionBundleContext(this);
+        }
+
+    }
+
+}

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/DataModelMapper.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/DataModelMapper.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/DataModelMapper.java
index 3436662..6974cb9 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/DataModelMapper.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/DataModelMapper.java
@@ -19,11 +19,19 @@ package org.apache.nifi.registry.service;
 import org.apache.nifi.registry.bucket.Bucket;
 import org.apache.nifi.registry.db.entity.BucketEntity;
 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.FlowEntity;
 import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
 import org.apache.nifi.registry.db.entity.KeyEntity;
 import org.apache.nifi.registry.diff.ComponentDifference;
 import org.apache.nifi.registry.diff.ComponentDifferenceGroup;
+import org.apache.nifi.registry.extension.ExtensionBundle;
+import org.apache.nifi.registry.extension.ExtensionBundleType;
+import org.apache.nifi.registry.extension.ExtensionBundleVersionDependency;
+import org.apache.nifi.registry.extension.ExtensionBundleVersionMetadata;
 import org.apache.nifi.registry.flow.VersionedComponent;
 import org.apache.nifi.registry.flow.VersionedFlow;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
@@ -145,6 +153,122 @@ public class DataModelMapper {
         return null;
     }
 
+    // -- Map ExtensionBundleType
+
+    public static ExtensionBundleEntityType map(final ExtensionBundleType 
bundleType) {
+        switch (bundleType) {
+            case NIFI_NAR:
+                return ExtensionBundleEntityType.NIFI_NAR;
+
+            case MINIFI_CPP:
+                return ExtensionBundleEntityType.MINIFI_CPP;
+            default:
+                throw new IllegalArgumentException("Unknown bundle type: " + 
bundleType);
+        }
+    }
+
+    public static ExtensionBundleType map(final ExtensionBundleEntityType 
bundleEntityType) {
+        switch (bundleEntityType) {
+            case NIFI_NAR:
+                return ExtensionBundleType.NIFI_NAR;
+            case MINIFI_CPP:
+                return ExtensionBundleType.MINIFI_CPP;
+            default:
+                throw new IllegalArgumentException("Unknown bundle type: " + 
bundleEntityType);
+        }
+    }
+
+    // -- Map ExtensionBundle
+
+    public static ExtensionBundleEntity map(final ExtensionBundle bundle) {
+        final ExtensionBundleEntity entity = new ExtensionBundleEntity();
+        entity.setId(bundle.getIdentifier());
+        entity.setName(bundle.getName());
+        entity.setDescription(bundle.getDescription());
+        entity.setCreated(new Date(bundle.getCreatedTimestamp()));
+        entity.setModified(new Date(bundle.getModifiedTimestamp()));
+        entity.setType(BucketItemEntityType.EXTENSION_BUNDLE);
+        entity.setBucketId(bundle.getBucketIdentifier());
+
+        entity.setGroupId(bundle.getGroupId());
+        entity.setArtifactId(bundle.getArtifactId());
+        entity.setBundleType(map(bundle.getBundleType()));
+        return entity;
+    }
+
+    public static ExtensionBundle map(final BucketEntity bucketEntity, final 
ExtensionBundleEntity bundleEntity) {
+        final ExtensionBundle bundle = new ExtensionBundle();
+        bundle.setIdentifier(bundleEntity.getId());
+        bundle.setName(bundleEntity.getName());
+        bundle.setDescription(bundleEntity.getDescription());
+        bundle.setCreatedTimestamp(bundleEntity.getCreated().getTime());
+        bundle.setModifiedTimestamp(bundleEntity.getModified().getTime());
+        bundle.setBucketIdentifier(bundleEntity.getBucketId());
+
+        if (bucketEntity != null) {
+            bundle.setBucketName(bucketEntity.getName());
+        } else {
+            bundle.setBucketName(bundleEntity.getBucketName());
+        }
+
+        bundle.setGroupId(bundleEntity.getGroupId());
+        bundle.setArtifactId(bundleEntity.getArtifactId());
+        bundle.setBundleType(map(bundleEntity.getBundleType()));
+        bundle.setVersionCount(bundleEntity.getVersionCount());
+        return bundle;
+    }
+
+    // -- Map ExtensionBundleVersion
+
+    public static ExtensionBundleVersionEntity map(final 
ExtensionBundleVersionMetadata bundleVersionMetadata) {
+        final ExtensionBundleVersionEntity entity = new 
ExtensionBundleVersionEntity();
+        entity.setId(bundleVersionMetadata.getId());
+        
entity.setExtensionBundleId(bundleVersionMetadata.getExtensionBundleId());
+        entity.setVersion(bundleVersionMetadata.getVersion());
+        entity.setCreated(new Date(bundleVersionMetadata.getTimestamp()));
+        entity.setCreatedBy(bundleVersionMetadata.getAuthor());
+        entity.setDescription(bundleVersionMetadata.getDescription());
+        entity.setSha256Hex(bundleVersionMetadata.getSha256());
+        entity.setSha256Supplied(bundleVersionMetadata.getSha256Supplied());
+        return entity;
+    }
+
+    public static ExtensionBundleVersionMetadata map(final BucketEntity 
bucketEntity, final ExtensionBundleVersionEntity bundleVersionEntity) {
+        final ExtensionBundleVersionMetadata bundleVersionMetadata = new 
ExtensionBundleVersionMetadata();
+        bundleVersionMetadata.setId(bundleVersionEntity.getId());
+        
bundleVersionMetadata.setExtensionBundleId(bundleVersionEntity.getExtensionBundleId());
+        bundleVersionMetadata.setVersion(bundleVersionEntity.getVersion());
+        
bundleVersionMetadata.setTimestamp(bundleVersionEntity.getCreated().getTime());
+        bundleVersionMetadata.setAuthor(bundleVersionEntity.getCreatedBy());
+        
bundleVersionMetadata.setDescription(bundleVersionEntity.getDescription());
+        bundleVersionMetadata.setSha256(bundleVersionEntity.getSha256Hex());
+        
bundleVersionMetadata.setSha256Supplied(bundleVersionEntity.getSha256Supplied());
+
+        if (bucketEntity != null) {
+            bundleVersionMetadata.setBucketId(bucketEntity.getId());
+        }
+
+        return bundleVersionMetadata;
+    }
+
+    // -- Map ExtensionBundleVersionDependency
+
+    public static ExtensionBundleVersionDependencyEntity map(final 
ExtensionBundleVersionDependency bundleVersionDependency) {
+        final ExtensionBundleVersionDependencyEntity entity = new 
ExtensionBundleVersionDependencyEntity();
+        entity.setGroupId(bundleVersionDependency.getGroupId());
+        entity.setArtifactId(bundleVersionDependency.getArtifactId());
+        entity.setVersion(bundleVersionDependency.getVersion());
+        return entity;
+    }
+
+    public static ExtensionBundleVersionDependency map(final 
ExtensionBundleVersionDependencyEntity dependencyEntity) {
+        final ExtensionBundleVersionDependency dependency = new 
ExtensionBundleVersionDependency();
+        dependency.setGroupId(dependencyEntity.getGroupId());
+        dependency.setArtifactId(dependencyEntity.getArtifactId());
+        dependency.setVersion(dependencyEntity.getVersion());
+        return dependency;
+    }
+
     // --- Map keys
 
     public static Key map(final KeyEntity keyEntity) {

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/ExtensionBundleMetadataExtractors.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/ExtensionBundleMetadataExtractors.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/ExtensionBundleMetadataExtractors.java
new file mode 100644
index 0000000..2746c0c
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/ExtensionBundleMetadataExtractors.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.apache.nifi.registry.extension.BundleExtractor;
+import org.apache.nifi.registry.extension.ExtensionBundleType;
+import org.apache.nifi.registry.extension.minificpp.MiNiFiCppBundleExtractor;
+import org.apache.nifi.registry.extension.nar.NarBundleExtractor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class ExtensionBundleMetadataExtractors {
+
+    private Map<ExtensionBundleType, BundleExtractor> extractors;
+
+    @Bean
+    public synchronized Map<ExtensionBundleType, BundleExtractor> 
getExtractors() {
+        if (extractors == null) {
+            extractors = new HashMap<>();
+            extractors.put(ExtensionBundleType.NIFI_NAR, new 
NarBundleExtractor());
+            extractors.put(ExtensionBundleType.MINIFI_CPP, new 
MiNiFiCppBundleExtractor());
+        }
+
+        return extractors;
+    }
+
+}

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/MetadataService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/MetadataService.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/MetadataService.java
index ea0b214..1dc90d4 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/MetadataService.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/MetadataService.java
@@ -18,6 +18,11 @@ package org.apache.nifi.registry.service;
 
 import org.apache.nifi.registry.db.entity.BucketEntity;
 import org.apache.nifi.registry.db.entity.BucketItemEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntity;
+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;
 
@@ -215,6 +220,234 @@ public interface MetadataService {
     // 
--------------------------------------------------------------------------------------------
 
     /**
+     * Creates the given extension bundle.
+     *
+     * @param extensionBundle the extension bundle to create
+     * @return the created extension bundle
+     */
+    ExtensionBundleEntity createExtensionBundle(ExtensionBundleEntity 
extensionBundle);
+
+    /**
+     * Retrieves the extension bundle with the given id.
+     *
+     * @param extensionBundleId the id of the extension bundle
+     * @return the extension bundle with the id, or null if one does not exist
+     */
+    ExtensionBundleEntity getExtensionBundle(String extensionBundleId);
+
+    /**
+     * Retrieves the extension bundle in the given bucket with the given group 
and artifact id.
+     *
+     * @return the extension bundle, or null if one does not exist
+     */
+    ExtensionBundleEntity getExtensionBundle(String bucketId, String groupId, 
String artifactId);
+
+    /**
+     * Retrieves all extension bundles in the buckets with the given bucket 
ids.
+     *
+     * @param bucketIds the bucket ids
+     * @return the list of all extension bundles in the given buckets
+     */
+    List<ExtensionBundleEntity> getExtensionBundles(Set<String> bucketIds);
+
+    /**
+     * Retrieves the extension bundles for the given bucket.
+     *
+     * @param bucketId the bucket id
+     * @return the list of extension bundles for the bucket
+     */
+    List<ExtensionBundleEntity> getExtensionBundlesByBucket(String bucketId);
+
+    /**
+     * Retrieves the extension bundles for the given bucket and group.
+     *
+     * @param bucketId the bucket id
+     * @param groupId the group id
+     * @return the list of extension bundles for the bucket and group
+     */
+    List<ExtensionBundleEntity> getExtensionBundlesByBucketAndGroup(String 
bucketId, String groupId);
+
+    /**
+     * Deletes the given extension bundle.
+     *
+     * @param extensionBundle the extension bundle to delete
+     */
+    void deleteExtensionBundle(ExtensionBundleEntity extensionBundle);
+
+    /**
+     * Deletes the extension bundle with the given id.
+     *
+     * @param extensionBundleId the id extension bundle to delete
+     */
+    void deleteExtensionBundle(String extensionBundleId);
+
+    // 
--------------------------------------------------------------------------------------------
+
+    /**
+     * Creates a version of an extension bundle.
+     *
+     * @param extensionBundleVersion the bundle version to create
+     * @return the created bundle version
+     */
+    ExtensionBundleVersionEntity 
createExtensionBundleVersion(ExtensionBundleVersionEntity 
extensionBundleVersion);
+
+    /**
+     * Retrieves the extension bundle version for the given bundle id and 
version.
+     *
+     * @param extensionBundleId the id of the extension bundle
+     * @param version the version of the extension bundle
+     * @return the extension bundle version, or null if does not exist
+     */
+    ExtensionBundleVersionEntity getExtensionBundleVersion(String 
extensionBundleId, String version);
+
+    /**
+     * Retrieves the extension bundle version by bucket, group, artifact, 
version.
+     *
+     * @param bucketId the bucket id
+     * @param groupId the group id
+     * @param artifactId the artifact id
+     * @param version the version
+     * @return the extension bundle version, or null if does not exist
+     */
+    ExtensionBundleVersionEntity getExtensionBundleVersion(String bucketId, 
String groupId, String artifactId, String version);
+
+    /**
+     * Retrieves the extension bundle versions for the given extension bundle 
id.
+     *
+     * @param extensionBundleId the extension bundle id
+     * @return the list of extension bundle versions
+     */
+    List<ExtensionBundleVersionEntity> getExtensionBundleVersions(String 
extensionBundleId);
+
+    /**
+     * Retrieves the extension bundle version with the given group id and 
artifact id in the given bucket.
+     *
+     * @param bucketId the bucket id
+     * @param groupId the group id
+     * @param artifactId the artifact id
+     * @return the list of extension bundles
+     */
+    List<ExtensionBundleVersionEntity> getExtensionBundleVersions(String 
bucketId, String groupId, String artifactId);
+
+    /**
+     * Retrieves the extension bundle versions with the given group id, 
artifact id, and version across all buckets.
+     *
+     * @param groupId the group id
+     * @param artifactId the artifact id
+     * @param version the versions
+     * @return all bundle versions for the group id, artifact id, and version
+     */
+    List<ExtensionBundleVersionEntity> getExtensionBundleVersionsGlobal(String 
groupId, String artifactId, String version);
+
+    /**
+     * Deletes the extension bundle version.
+     *
+     * @param extensionBundleVersion the extension bundle version to delete
+     */
+    void deleteExtensionBundleVersion(ExtensionBundleVersionEntity 
extensionBundleVersion);
+
+    /**
+     * Deletes the extension bundle version.
+     *
+     * @param extensionBundleVersionId the id of the extension bundle version
+     */
+    void deleteExtensionBundleVersion(String extensionBundleVersionId);
+
+    // 
--------------------------------------------------------------------------------------------
+
+    /**
+     * Creates the given extension bundle version dependency.
+     *
+     * @param dependencyEntity the dependency entity
+     * @return the created dependency
+     */
+    ExtensionBundleVersionDependencyEntity 
createDependency(ExtensionBundleVersionDependencyEntity dependencyEntity);
+
+    /**
+     * Retrieves the bundle dependencies for the given bundle version.
+     *
+     * @param extensionBundleVersionId the id of the extension bundle version
+     * @return the list of dependencies
+     */
+    List<ExtensionBundleVersionDependencyEntity> 
getDependenciesForBundleVersion(String extensionBundleVersionId);
+
+    // 
--------------------------------------------------------------------------------------------
+
+    /**
+     * Creates the given extension.
+     *
+     * @param extension the extension to create
+     * @return the created extension
+     */
+    ExtensionEntity createExtension(ExtensionEntity extension);
+
+    /**
+     * Retrieves the extension with the given id.
+     *
+     * @param id the id of the extension
+     * @return the extension with the id, or null if one does not exist
+     */
+    ExtensionEntity getExtensionById(String id);
+
+    /**
+     * Retrieves all extensions.
+     *
+     * @return the list of all extensions
+     */
+    List<ExtensionEntity> getAllExtensions();
+
+    /**
+     * Retrieves the extensions for the given extension bundle version.
+     *
+     * @param extensionBundleVersionId the id of the extension bundle version
+     * @return the extensions in the given bundle
+     */
+    List<ExtensionEntity> getExtensionsByBundleVersionId(String 
extensionBundleVersionId);
+
+    /**
+     * Retrieves the extensions for the bundle in the given bucket with the 
given group, artifact, and version.
+     *
+     * @param bucketId the bucket of the bundle
+     * @param groupId the group of the bundle
+     * @param artifactId the artifact id of the bundle
+     * @param version the version of the bundle
+     * @return the extensions for the bundle
+     */
+    List<ExtensionEntity> getExtensionsByBundleCoordinate(String bucketId, 
String groupId, String artifactId, String version);
+
+    /**
+     * Retrieves the extensions for the given category (i.e. processor, 
controller service, reporting task).
+     *
+     * @param category the category
+     * @return the extensions for the given category
+     */
+    List<ExtensionEntity> getExtensionsByCategory(ExtensionEntityCategory 
category);
+
+    /**
+     * Retrieves the extensions with the given tag.
+     *
+     * @param tag the tag
+     * @return the extensions with the given tag
+     */
+    List<ExtensionEntity> getExtensionsByTag(String tag);
+
+    /**
+     * Retrieves the set of all extension tags.
+     *
+     * @return the set of all extension tags
+     */
+    Set<String> getAllExtensionTags();
+
+    /**
+     * Deletes the given extension.
+     *
+     * @param extension the extension to delete
+     */
+    void deleteExtension(ExtensionEntity extension);
+
+    // 
--------------------------------------------------------------------------------------------
+
+    /**
      * @return the set of field names for Buckets
      */
     Set<String> getBucketFields();

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/RegistryService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
index 23f1d14..091803f 100644
--- 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
@@ -23,11 +23,20 @@ import org.apache.nifi.registry.bucket.Bucket;
 import org.apache.nifi.registry.bucket.BucketItem;
 import org.apache.nifi.registry.db.entity.BucketEntity;
 import org.apache.nifi.registry.db.entity.BucketItemEntity;
+import org.apache.nifi.registry.db.entity.ExtensionBundleEntity;
 import org.apache.nifi.registry.db.entity.FlowEntity;
 import org.apache.nifi.registry.db.entity.FlowSnapshotEntity;
 import org.apache.nifi.registry.diff.ComponentDifferenceGroup;
 import org.apache.nifi.registry.diff.VersionedFlowDifference;
 import org.apache.nifi.registry.exception.ResourceNotFoundException;
+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 org.apache.nifi.registry.flow.FlowPersistenceProvider;
 import org.apache.nifi.registry.flow.FlowSnapshotContext;
 import org.apache.nifi.registry.flow.VersionedComponent;
@@ -44,6 +53,8 @@ import 
org.apache.nifi.registry.flow.diff.StandardComparableDataFlow;
 import org.apache.nifi.registry.flow.diff.StandardFlowComparator;
 import org.apache.nifi.registry.provider.flow.StandardFlowSnapshotContext;
 import org.apache.nifi.registry.serialization.Serializer;
+import 
org.apache.nifi.registry.service.extension.ExtensionBundleVersionCoordinate;
+import org.apache.nifi.registry.service.extension.ExtensionService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -55,7 +66,9 @@ import javax.validation.ConstraintViolationException;
 import javax.validation.Validator;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -85,6 +98,7 @@ public class RegistryService {
     private final MetadataService metadataService;
     private final FlowPersistenceProvider flowPersistenceProvider;
     private final Serializer<VersionedProcessGroup> processGroupSerializer;
+    private final ExtensionService extensionService;
     private final Validator validator;
 
     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@@ -95,14 +109,17 @@ public class RegistryService {
     public RegistryService(final MetadataService metadataService,
                            final FlowPersistenceProvider 
flowPersistenceProvider,
                            final Serializer<VersionedProcessGroup> 
processGroupSerializer,
+                           final ExtensionService extensionService,
                            final Validator validator) {
         this.metadataService = metadataService;
         this.flowPersistenceProvider = flowPersistenceProvider;
         this.processGroupSerializer = processGroupSerializer;
+        this.extensionService = extensionService;
         this.validator = validator;
         Validate.notNull(this.metadataService);
         Validate.notNull(this.flowPersistenceProvider);
         Validate.notNull(this.processGroupSerializer);
+        Validate.notNull(this.extensionService);
         Validate.notNull(this.validator);
     }
 
@@ -159,6 +176,25 @@ public class RegistryService {
         }
     }
 
+    public Bucket getBucketByName(final String bucketName) {
+        if (bucketName == null) {
+            throw new IllegalArgumentException("Bucket name cannot be null");
+        }
+
+        readLock.lock();
+        try {
+            final List<BucketEntity> buckets = 
metadataService.getBucketsByName(bucketName);
+            if (buckets.isEmpty()) {
+                LOGGER.warn("The specified bucket name [{}] does not exist.", 
bucketName);
+                throw new ResourceNotFoundException("The specified bucket name 
does not exist in this registry.");
+            }
+
+            return DataModelMapper.map(buckets.get(0));
+        } finally {
+            readLock.unlock();
+        }
+    }
+
     public List<Bucket> getBuckets() {
         readLock.lock();
         try {
@@ -298,11 +334,13 @@ public class RegistryService {
     }
 
     private void addBucketItem(final List<BucketItem> bucketItems, final 
BucketItemEntity itemEntity) {
+        // Currently we don't populate the bucket name for items so we pass in 
null in the map methods
         if (itemEntity instanceof FlowEntity) {
             final FlowEntity flowEntity = (FlowEntity) itemEntity;
-
-            // Currently we don't populate the bucket name for items
             bucketItems.add(DataModelMapper.map(null, flowEntity));
+        } else if (itemEntity instanceof ExtensionBundleEntity) {
+            final ExtensionBundleEntity bundleEntity = (ExtensionBundleEntity) 
itemEntity;
+            bucketItems.add(DataModelMapper.map(null, bundleEntity));
         } else {
             LOGGER.error("Unknown type of BucketItemEntity: " + 
itemEntity.getClass().getCanonicalName());
         }
@@ -977,6 +1015,128 @@ public class RegistryService {
         return differenceGroups.values().stream().collect(Collectors.toSet());
     }
 
+    // ---------------------- ExtensionBundle methods 
---------------------------------------------
+
+    public ExtensionBundleVersion createExtensionBundleVersion(final String 
bucketIdentifier, final ExtensionBundleType bundleType,
+                                                               final 
InputStream inputStream, final String clientSha256) throws IOException {
+        writeLock.lock();
+        try {
+            return 
extensionService.createExtensionBundleVersion(bucketIdentifier, bundleType, 
inputStream, clientSha256);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public List<ExtensionBundle> getExtensionBundles(Set<String> 
bucketIdentifiers) {
+        readLock.lock();
+        try {
+            return extensionService.getExtensionBundles(bucketIdentifiers);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public List<ExtensionBundle> getExtensionBundlesByBucket(final String 
bucketIdentifier) {
+        readLock.lock();
+        try {
+            return 
extensionService.getExtensionBundlesByBucket(bucketIdentifier);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public ExtensionBundle getExtensionBundle(final String extensionBundleId) {
+        readLock.lock();
+        try {
+            return extensionService.getExtensionBundle(extensionBundleId);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public ExtensionBundle deleteExtensionBundle(final ExtensionBundle 
extensionBundle) {
+        writeLock.lock();
+        try {
+            return extensionService.deleteExtensionBundle(extensionBundle);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    public SortedSet<ExtensionBundleVersionMetadata> 
getExtensionBundleVersions(final String extensionBundleIdentifier) {
+        readLock.lock();
+        try {
+            return 
extensionService.getExtensionBundleVersions(extensionBundleIdentifier);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public ExtensionBundleVersion 
getExtensionBundleVersion(ExtensionBundleVersionCoordinate versionCoordinate) {
+        readLock.lock();
+        try {
+            return 
extensionService.getExtensionBundleVersion(versionCoordinate);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public void writeExtensionBundleVersionContent(final 
ExtensionBundleVersion bundleVersion, final OutputStream out) {
+        readLock.lock();
+        try {
+            extensionService.writeExtensionBundleVersionContent(bundleVersion, 
out);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public ExtensionBundleVersion deleteExtensionBundleVersion(final 
ExtensionBundleVersion bundleVersion) {
+        writeLock.lock();
+        try {
+            return 
extensionService.deleteExtensionBundleVersion(bundleVersion);
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    // ---------------------- Extension Repository methods 
---------------------------------------------
+
+    public SortedSet<ExtensionRepoBucket> getExtensionRepoBuckets(final 
Set<String> bucketIds) {
+        readLock.lock();
+        try {
+            return extensionService.getExtensionRepoBuckets(bucketIds);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public SortedSet<ExtensionRepoGroup> getExtensionRepoGroups(final Bucket 
bucket) {
+        readLock.lock();
+        try {
+            return extensionService.getExtensionRepoGroups(bucket);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public SortedSet<ExtensionRepoArtifact> getExtensionRepoArtifacts(final 
Bucket bucket, final String groupId) {
+        readLock.lock();
+        try {
+            return extensionService.getExtensionRepoArtifacts(bucket, groupId);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    public SortedSet<ExtensionRepoVersionSummary> 
getExtensionRepoVersions(final Bucket bucket, final String groupId, final 
String artifactId) {
+        readLock.lock();
+        try {
+            return extensionService.getExtensionRepoVersions(bucket, groupId, 
artifactId);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
     // ---------------------- Field methods 
---------------------------------------------
 
     public Set<String> getBucketFields() {

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/ExtensionBundleCoordinate.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionBundleCoordinate.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionBundleCoordinate.java
new file mode 100644
index 0000000..ce78ce0
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionBundleCoordinate.java
@@ -0,0 +1,57 @@
+/*
+ * 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.lang3.Validate;
+
+/**
+ * The unique coordinate for an extension bundle.
+ *
+ * This is an alternative to using the single uuid identifier for the bundle.
+ */
+public class ExtensionBundleCoordinate {
+
+    private final String bucketId;
+    private final String groupId;
+    private final String artifactId;
+
+    public ExtensionBundleCoordinate(final String bucketId, final String 
groupId, final String artifactId) {
+        this.bucketId = bucketId;
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        Validate.notBlank(this.bucketId, "Bucket id cannot be null or blank");
+        Validate.notBlank(this.groupId, "Group id cannot be null or blank");
+        Validate.notBlank(this.artifactId, "Artifact id cannot be null or 
blank");
+    }
+
+    public String getBucketId() {
+        return bucketId;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    @Override
+    public String toString() {
+        return bucketId + ":" + groupId + ":" + 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/ExtensionBundleVersionCoordinate.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionBundleVersionCoordinate.java
 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionBundleVersionCoordinate.java
new file mode 100644
index 0000000..38904e5
--- /dev/null
+++ 
b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/extension/ExtensionBundleVersionCoordinate.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.service.extension;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * The unique coordinate for a version of an extension bundle.
+ */
+public class ExtensionBundleVersionCoordinate extends 
ExtensionBundleCoordinate {
+
+    private final String version;
+
+    public ExtensionBundleVersionCoordinate(final String bucketId, final 
String groupId, final String artifactId, final String version) {
+        super(bucketId, groupId, artifactId);
+        this.version = version;
+        Validate.notBlank(this.version, "Version cannot be null or blank");
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + ":" + version;
+    }
+}

Reply via email to