Repository: nifi-registry
Updated Branches:
  refs/heads/master 8cb681fe3 -> 5e918f77c


NIFIREG-158 Added ability to retrieve flow directly by id without knowing the 
bucket
- Added /flows/{flowId}
- Added /flows/{flowId}/versions/{version}
- Added /flows/{flowId}/versions/latest
- Added /flows/{flowId}/versions/latest/metadata
- Added /flows/{flowId}/versions
- Adding IT tests

This closes #108.

Signed-off-by: Kevin Doran <kdo...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/nifi-registry/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi-registry/commit/5e918f77
Tree: http://git-wip-us.apache.org/repos/asf/nifi-registry/tree/5e918f77
Diff: http://git-wip-us.apache.org/repos/asf/nifi-registry/diff/5e918f77

Branch: refs/heads/master
Commit: 5e918f77c2ed8ccfab835c68ba40c4153bef7c7c
Parents: 8cb681f
Author: Bryan Bende <bbe...@apache.org>
Authored: Thu Apr 5 16:22:08 2018 -0400
Committer: Kevin Doran <kdo...@apache.org>
Committed: Mon Apr 9 15:04:37 2018 -0400

----------------------------------------------------------------------
 .../apache/nifi/registry/client/FlowClient.java |   9 +-
 .../registry/client/FlowSnapshotClient.java     |  43 ++++
 .../registry/client/impl/JerseyFlowClient.java  |  15 +-
 .../client/impl/JerseyFlowSnapshotClient.java   |  92 ++++++-
 .../nifi/registry/service/RegistryService.java  | 117 ++++++---
 .../registry/service/TestRegistryService.java   |  36 +++
 .../nifi/registry/web/api/FlowResource.java     | 247 ++++++++++++++++++-
 .../web/api/SecureNiFiRegistryClientIT.java     |  54 +++-
 .../web/api/UnsecuredNiFiRegistryClientIT.java  |  35 +++
 .../resources/conf/secure-file/authorizers.xml  |   1 +
 10 files changed, 588 insertions(+), 61 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
index 150927a..6dd72e9 100644
--- 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
+++ 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
@@ -52,17 +52,14 @@ public interface FlowClient {
     VersionedFlow get(String bucketId, String flowId) throws 
NiFiRegistryException, IOException;
 
     /**
-     * Gets the flow with the given id in the given bucket.
-     *
-     * The list of snapshot metadata will be populated.
+     * Gets the flow with the given id.
      *
-     * @param bucketId a bucket id
      * @param flowId a flow id
-     * @return the flow with the given id in the given bucket
+     * @return the flow with the given id
      * @throws NiFiRegistryException if an error is encountered other than 
IOException
      * @throws IOException if an I/O error is encountered
      */
-    VersionedFlow getWithSnapshots(String bucketId, String flowId) throws 
NiFiRegistryException, IOException;
+    VersionedFlow get(String flowId) throws NiFiRegistryException, IOException;
 
     /**
      * Updates the given flow with in the given bucket.

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
index 1141753..edf7beb 100644
--- 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
+++ 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
@@ -53,6 +53,17 @@ public interface FlowSnapshotClient {
     VersionedFlowSnapshot get(String bucketId, String flowId, int version) 
throws NiFiRegistryException, IOException;
 
     /**
+     * Gets the snapshot for the given flow and version.
+     *
+     * @param flowId the flow id
+     * @param version the version
+     * @return the snapshot with the given version of the given flow
+     * @throws NiFiRegistryException if an error is encountered other than 
IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlowSnapshot get(String flowId, int version) throws 
NiFiRegistryException, IOException;
+
+    /**
      * Gets the latest snapshot for the given flow.
      *
      * @param bucketId the bucket id
@@ -64,6 +75,16 @@ public interface FlowSnapshotClient {
     VersionedFlowSnapshot getLatest(String bucketId, String flowId) throws 
NiFiRegistryException, IOException;
 
     /**
+     * Gets the latest snapshot for the given flow.
+     *
+     * @param flowId the flow id
+     * @return the snapshot with the latest version for the given flow
+     * @throws NiFiRegistryException if an error is encountered other than 
IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlowSnapshot getLatest(String flowId) throws 
NiFiRegistryException, IOException;
+
+    /**
      * Gets the latest snapshot metadata for the given flow.
      *
      * @param bucketId the bucket id
@@ -75,6 +96,16 @@ public interface FlowSnapshotClient {
     VersionedFlowSnapshotMetadata getLatestMetadata(String bucketId, String 
flowId) throws NiFiRegistryException, IOException;
 
     /**
+     * Gets the latest snapshot metadata for the given flow.
+     *
+     * @param flowId the flow id
+     * @return the snapshot metadata for the latest version of the given flow
+     * @throws NiFiRegistryException if an error is encountered other than 
IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlowSnapshotMetadata getLatestMetadata(String flowId) throws 
NiFiRegistryException, IOException;
+
+    /**
      * Gets a list of the metadata for all snapshots of a given flow.
      *
      * The contents of each snapshot are not part of the response.
@@ -87,4 +118,16 @@ public interface FlowSnapshotClient {
      */
     List<VersionedFlowSnapshotMetadata> getSnapshotMetadata(String bucketId, 
String flowId) throws NiFiRegistryException, IOException;
 
+    /**
+     * Gets a list of the metadata for all snapshots of a given flow.
+     *
+     * The contents of each snapshot are not part of the response.
+     *
+     * @param flowId the flow id
+     * @return the list of snapshot metadata
+     * @throws NiFiRegistryException if an error is encountered other than 
IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    List<VersionedFlowSnapshotMetadata> getSnapshotMetadata(String flowId) 
throws NiFiRegistryException, IOException;
+
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
index 08e8cbb..486a20a 100644
--- 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
+++ 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
@@ -94,23 +94,18 @@ public class JerseyFlowClient extends AbstractJerseyClient  
implements FlowClien
     }
 
     @Override
-    public VersionedFlow getWithSnapshots(final String bucketId, final String 
flowId) throws NiFiRegistryException, IOException {
-        if (StringUtils.isBlank(bucketId)) {
-            throw new IllegalArgumentException("Bucket Identifier cannot be 
blank");
-        }
-
+    public VersionedFlow get(final String flowId) throws 
NiFiRegistryException, IOException {
         if (StringUtils.isBlank(flowId)) {
             throw new IllegalArgumentException("Flow Identifier cannot be 
blank");
         }
 
+        // this uses the flowsTarget because its calling /flows/{flowId} 
without knowing a bucketId
         return executeAction("Error retrieving flow", () -> {
-            final WebTarget target = bucketFlowsTarget
+            final WebTarget target = flowsTarget
                     .path("/{flowId}")
-                    .resolveTemplate("bucketId", bucketId)
-                    .resolveTemplate("flowId", flowId)
-                    .queryParam("verbose", "true");
+                    .resolveTemplate("flowId", flowId);
 
-            return getRequestBuilder(target).get(VersionedFlow.class);
+            return  getRequestBuilder(target).get(VersionedFlow.class);
         });
     }
 

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
index deddd5e..befe389 100644
--- 
a/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
+++ 
b/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
@@ -36,7 +36,8 @@ import java.util.Map;
  */
 public class JerseyFlowSnapshotClient extends AbstractJerseyClient implements 
FlowSnapshotClient {
 
-    final WebTarget flowSnapshotTarget;
+    final WebTarget bucketFlowSnapshotTarget;
+    final WebTarget flowsFlowSnapshotTarget;
 
     public JerseyFlowSnapshotClient(final WebTarget baseTarget) {
         this(baseTarget, Collections.emptyMap());
@@ -44,10 +45,10 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
 
     public JerseyFlowSnapshotClient(final WebTarget baseTarget, final 
Map<String,String> headers) {
         super(headers);
-        this.flowSnapshotTarget = 
baseTarget.path("/buckets/{bucketId}/flows/{flowId}/versions");
+        this.bucketFlowSnapshotTarget = 
baseTarget.path("/buckets/{bucketId}/flows/{flowId}/versions");
+        this.flowsFlowSnapshotTarget = 
baseTarget.path("/flows/{flowId}/versions");
     }
 
-
     @Override
     public VersionedFlowSnapshot create(final VersionedFlowSnapshot snapshot)
             throws NiFiRegistryException, IOException {
@@ -66,7 +67,7 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
         }
 
         return executeAction("Error creating snapshot", () -> {
-            final WebTarget target = flowSnapshotTarget
+            final WebTarget target = bucketFlowSnapshotTarget
                     .resolveTemplate("bucketId", bucketId)
                     .resolveTemplate("flowId", flowId);
 
@@ -94,7 +95,7 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
         }
 
         return executeAction("Error retrieving flow snapshot", () -> {
-            final WebTarget target = flowSnapshotTarget
+            final WebTarget target = bucketFlowSnapshotTarget
                     .path("/{version}")
                     .resolveTemplate("bucketId", bucketId)
                     .resolveTemplate("flowId", flowId)
@@ -105,6 +106,28 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
     }
 
     @Override
+    public VersionedFlowSnapshot get(final String flowId, final int version)
+            throws NiFiRegistryException, IOException {
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be 
blank");
+        }
+
+        if (version < 1) {
+            throw new IllegalArgumentException("Version must be greater than 
1");
+        }
+
+        return executeAction("Error retrieving flow snapshot", () -> {
+            final WebTarget target = flowsFlowSnapshotTarget
+                    .path("/{version}")
+                    .resolveTemplate("flowId", flowId)
+                    .resolveTemplate("version", version);
+
+            return getRequestBuilder(target).get(VersionedFlowSnapshot.class);
+        });
+    }
+
+    @Override
     public VersionedFlowSnapshot getLatest(final String bucketId, final String 
flowId)
             throws NiFiRegistryException, IOException {
         if (StringUtils.isBlank(bucketId)) {
@@ -116,7 +139,7 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
         }
 
         return executeAction("Error retrieving latest snapshot", () -> {
-            final WebTarget target = flowSnapshotTarget
+            final WebTarget target = bucketFlowSnapshotTarget
                     .path("/latest")
                     .resolveTemplate("bucketId", bucketId)
                     .resolveTemplate("flowId", flowId);
@@ -126,7 +149,23 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
     }
 
     @Override
-    public VersionedFlowSnapshotMetadata getLatestMetadata(String bucketId, 
String flowId) throws NiFiRegistryException, IOException {
+    public VersionedFlowSnapshot getLatest(final String flowId)
+            throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be 
blank");
+        }
+
+        return executeAction("Error retrieving latest snapshot", () -> {
+            final WebTarget target = flowsFlowSnapshotTarget
+                    .path("/latest")
+                    .resolveTemplate("flowId", flowId);
+
+            return getRequestBuilder(target).get(VersionedFlowSnapshot.class);
+        });
+    }
+
+    @Override
+    public VersionedFlowSnapshotMetadata getLatestMetadata(final String 
bucketId, final String flowId) throws NiFiRegistryException, IOException {
         if (StringUtils.isBlank(bucketId)) {
             throw new IllegalArgumentException("Bucket Identifier cannot be 
blank");
         }
@@ -136,7 +175,7 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
         }
 
         return executeAction("Error retrieving latest snapshot metadata", () 
-> {
-            final WebTarget target = flowSnapshotTarget
+            final WebTarget target = bucketFlowSnapshotTarget
                     .path("/latest/metadata")
                     .resolveTemplate("bucketId", bucketId)
                     .resolveTemplate("flowId", flowId);
@@ -146,6 +185,21 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
     }
 
     @Override
+    public VersionedFlowSnapshotMetadata getLatestMetadata(final String 
flowId) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be 
blank");
+        }
+
+        return executeAction("Error retrieving latest snapshot metadata", () 
-> {
+            final WebTarget target = flowsFlowSnapshotTarget
+                    .path("/latest/metadata")
+                    .resolveTemplate("flowId", flowId);
+
+            return 
getRequestBuilder(target).get(VersionedFlowSnapshotMetadata.class);
+        });
+    }
+
+    @Override
     @SuppressWarnings("unchecked")
     public List<VersionedFlowSnapshotMetadata> getSnapshotMetadata(final 
String bucketId, final String flowId)
             throws NiFiRegistryException, IOException {
@@ -158,7 +212,7 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
         }
 
         return executeAction("Error retrieving snapshot metadata", () -> {
-            final WebTarget target = flowSnapshotTarget
+            final WebTarget target = bucketFlowSnapshotTarget
                     .resolveTemplate("bucketId", bucketId)
                     .resolveTemplate("flowId", flowId);
 
@@ -169,4 +223,24 @@ public class JerseyFlowSnapshotClient extends 
AbstractJerseyClient implements Fl
         });
     }
 
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<VersionedFlowSnapshotMetadata> getSnapshotMetadata(final 
String flowId)
+            throws NiFiRegistryException, IOException {
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be 
blank");
+        }
+
+        return executeAction("Error retrieving snapshot metadata", () -> {
+            final WebTarget target = flowsFlowSnapshotTarget
+                    .resolveTemplate("flowId", flowId);
+
+            final VersionedFlowSnapshotMetadata[] snapshots = 
getRequestBuilder(target)
+                    .get(VersionedFlowSnapshotMetadata[].class);
+
+            return snapshots == null ? Collections.emptyList() : 
Arrays.asList(snapshots);
+        });
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
 
b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
index b031162..23f1d14 100644
--- 
a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
+++ 
b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/service/RegistryService.java
@@ -396,6 +396,26 @@ public class RegistryService {
         }
     }
 
+    public VersionedFlow getFlow(final String flowIdentifier) {
+        if (StringUtils.isBlank(flowIdentifier)) {
+            throw new IllegalArgumentException("Versioned flow identifier 
cannot be null or blank");
+        }
+
+        readLock.lock();
+        try {
+            final FlowEntity existingFlow = 
metadataService.getFlowByIdWithSnapshotCounts(flowIdentifier);
+            if (existingFlow == null) {
+                LOGGER.warn("The specified flow id [{}] does not exist.", 
flowIdentifier);
+                throw new ResourceNotFoundException("The specified flow ID 
does not exist.");
+            }
+
+            final BucketEntity existingBucket = 
metadataService.getBucketById(existingFlow.getBucketId());
+            return DataModelMapper.map(existingBucket, existingFlow);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
     public List<VersionedFlow> getFlows(final String bucketId) {
         if (StringUtils.isBlank(bucketId)) {
             throw new IllegalArgumentException("Bucket identifier cannot be 
null");
@@ -650,40 +670,44 @@ public class RegistryService {
                 throw new IllegalStateException("The requested flow is not 
located in the given bucket");
             }
 
-            // ensure the snapshot exists
-            final FlowSnapshotEntity snapshotEntity = 
metadataService.getFlowSnapshot(flowIdentifier, version);
-            if (snapshotEntity == null) {
-                LOGGER.warn("The specified flow snapshot id [{}] does not 
exist for version [{}].", flowIdentifier, version);
-                throw new ResourceNotFoundException("The specified versioned 
flow snapshot does not exist for this flow.");
-            }
-
-            // get the serialized bytes of the snapshot
-            final byte[] serializedSnapshot = 
flowPersistenceProvider.getFlowContent(bucketIdentifier, flowIdentifier, 
version);
+            return getVersionedFlowSnapshot(existingBucket, 
flowEntityWithCount, version);
+        } finally {
+            readLock.unlock();
+        }
+    }
 
-            if (serializedSnapshot == null || serializedSnapshot.length == 0) {
-                throw new IllegalStateException("No serialized content found 
for snapshot with flow identifier "
-                        + flowIdentifier + " and version " + version);
-            }
+    private VersionedFlowSnapshot getVersionedFlowSnapshot(final BucketEntity 
bucketEntity, final FlowEntity flowEntity, final Integer version) {
+        // ensure the snapshot exists
+        final FlowSnapshotEntity snapshotEntity = 
metadataService.getFlowSnapshot(flowEntity.getId(), version);
+        if (snapshotEntity == null) {
+            LOGGER.warn("The specified flow snapshot id [{}] does not exist 
for version [{}].", flowEntity.getId(), version);
+            throw new ResourceNotFoundException("The specified versioned flow 
snapshot does not exist for this flow.");
+        }
 
-            // deserialize the contents
-            final InputStream input = new 
ByteArrayInputStream(serializedSnapshot);
-            final VersionedProcessGroup flowContents = 
processGroupSerializer.deserialize(input);
+        // get the serialized bytes of the snapshot
+        final byte[] serializedSnapshot = 
flowPersistenceProvider.getFlowContent(bucketEntity.getId(), 
flowEntity.getId(), version);
 
-            // map entities to data model
-            final Bucket bucket = DataModelMapper.map(existingBucket);
-            final VersionedFlow versionedFlow = 
DataModelMapper.map(existingBucket, flowEntityWithCount);
-            final VersionedFlowSnapshotMetadata snapshotMetadata = 
DataModelMapper.map(existingBucket, snapshotEntity);
-
-            // create the snapshot to return
-            final VersionedFlowSnapshot snapshot = new VersionedFlowSnapshot();
-            snapshot.setFlowContents(flowContents);
-            snapshot.setSnapshotMetadata(snapshotMetadata);
-            snapshot.setFlow(versionedFlow);
-            snapshot.setBucket(bucket);
-            return snapshot;
-        } finally {
-            readLock.unlock();
+        if (serializedSnapshot == null || serializedSnapshot.length == 0) {
+            throw new IllegalStateException("No serialized content found for 
snapshot with flow identifier "
+                    + flowEntity.getId() + " and version " + version);
         }
+
+        // deserialize the contents
+        final InputStream input = new ByteArrayInputStream(serializedSnapshot);
+        final VersionedProcessGroup flowContents = 
processGroupSerializer.deserialize(input);
+
+        // map entities to data model
+        final Bucket bucket = DataModelMapper.map(bucketEntity);
+        final VersionedFlow versionedFlow = DataModelMapper.map(bucketEntity, 
flowEntity);
+        final VersionedFlowSnapshotMetadata snapshotMetadata = 
DataModelMapper.map(bucketEntity, snapshotEntity);
+
+        // create the snapshot to return
+        final VersionedFlowSnapshot snapshot = new VersionedFlowSnapshot();
+        snapshot.setFlowContents(flowContents);
+        snapshot.setSnapshotMetadata(snapshotMetadata);
+        snapshot.setFlow(versionedFlow);
+        snapshot.setBucket(bucket);
+        return snapshot;
     }
 
     /**
@@ -777,6 +801,39 @@ public class RegistryService {
         }
     }
 
+    public VersionedFlowSnapshotMetadata getLatestFlowSnapshotMetadata(final 
String flowIdentifier) {
+        if (StringUtils.isBlank(flowIdentifier)) {
+            throw new IllegalArgumentException("Flow identifier cannot be null 
or blank");
+        }
+
+        readLock.lock();
+        try {
+            // ensure the flow exists
+            final FlowEntity existingFlow = 
metadataService.getFlowById(flowIdentifier);
+            if (existingFlow == null) {
+                LOGGER.warn("The specified flow id [{}] does not exist.", 
flowIdentifier);
+                throw new ResourceNotFoundException("The specified flow ID 
does not exist in this bucket.");
+            }
+
+            // ensure the bucket exists
+            final BucketEntity existingBucket = 
metadataService.getBucketById(existingFlow.getBucketId());
+            if (existingBucket == null) {
+                LOGGER.warn("The specified bucket id [{}] does not exist.", 
existingFlow.getBucketId());
+                throw new ResourceNotFoundException("The specified bucket ID 
does not exist in this registry.");
+            }
+
+            // get latest snapshot for the flow
+            final FlowSnapshotEntity latestSnapshot = 
metadataService.getLatestSnapshot(existingFlow.getId());
+            if (latestSnapshot == null) {
+                throw new ResourceNotFoundException("The specified flow ID has 
no versions");
+            }
+
+            return DataModelMapper.map(existingBucket, latestSnapshot);
+        } finally {
+            readLock.unlock();
+        }
+    }
+
     public VersionedFlowSnapshotMetadata deleteFlowSnapshot(final String 
bucketIdentifier, final String flowIdentifier, final Integer version) {
         if (StringUtils.isBlank(bucketIdentifier)) {
             throw new IllegalArgumentException("Bucket identifier cannot be 
null or blank");

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
 
b/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
index c35cda7..95e2d1a 100644
--- 
a/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
+++ 
b/nifi-registry-framework/src/test/java/org/apache/nifi/registry/service/TestRegistryService.java
@@ -368,6 +368,12 @@ public class TestRegistryService {
         registryService.getFlow("bucket1","flow1");
     }
 
+    @Test(expected = ResourceNotFoundException.class)
+    public void testGetFlowDirectDoesNotExist() {
+        when(metadataService.getFlowById(any(String.class))).thenReturn(null);
+        registryService.getFlow("flow1");
+    }
+
     @Test
     public void testGetFlowExists() {
         final BucketEntity existingBucket = new BucketEntity();
@@ -399,6 +405,36 @@ public class TestRegistryService {
         assertEquals(flowEntity.getModified().getTime(), 
versionedFlow.getModifiedTimestamp());
     }
 
+    @Test
+    public void testGetFlowDirectExists() {
+        final BucketEntity existingBucket = new BucketEntity();
+        existingBucket.setId("b1");
+        existingBucket.setName("My Bucket");
+        existingBucket.setDescription("This is my bucket");
+        existingBucket.setCreated(new Date());
+
+        final FlowEntity flowEntity = new FlowEntity();
+        flowEntity.setId("flow1");
+        flowEntity.setName("My Flow");
+        flowEntity.setDescription("This is my flow.");
+        flowEntity.setCreated(new Date());
+        flowEntity.setModified(new Date());
+        flowEntity.setBucketId(existingBucket.getId());
+
+        
when(metadataService.getFlowByIdWithSnapshotCounts(flowEntity.getId())).thenReturn(flowEntity);
+        
when(metadataService.getBucketById(existingBucket.getId())).thenReturn(existingBucket);
+
+        final VersionedFlow versionedFlow = 
registryService.getFlow(flowEntity.getId());
+        assertNotNull(versionedFlow);
+        assertEquals(flowEntity.getId(), versionedFlow.getIdentifier());
+        assertEquals(flowEntity.getName(), versionedFlow.getName());
+        assertEquals(flowEntity.getDescription(), 
versionedFlow.getDescription());
+        assertEquals(flowEntity.getBucketId(), 
versionedFlow.getBucketIdentifier());
+        assertEquals(existingBucket.getName(), versionedFlow.getBucketName());
+        assertEquals(flowEntity.getCreated().getTime(), 
versionedFlow.getCreatedTimestamp());
+        assertEquals(flowEntity.getModified().getTime(), 
versionedFlow.getModifiedTimestamp());
+    }
+
     @Test(expected = ResourceNotFoundException.class)
     public void testGetFlowsByBucketDoesNotExist() {
         
when(metadataService.getBucketById(any(String.class))).thenReturn(null);

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
index c9deb11..2c04d60 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/FlowResource.java
@@ -18,19 +18,35 @@ package org.apache.nifi.registry.web.api;
 
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
 import io.swagger.annotations.Authorization;
+import io.swagger.annotations.Extension;
+import io.swagger.annotations.ExtensionProperty;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.registry.field.Fields;
+import org.apache.nifi.registry.flow.VersionedFlow;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import 
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.service.AuthorizationService;
 import org.apache.nifi.registry.service.RegistryService;
+import org.apache.nifi.registry.web.link.LinkService;
+import org.apache.nifi.registry.web.security.PermissionsService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Set;
+import java.util.SortedSet;
 
 @Component
 @Path("/flows")
@@ -39,13 +55,21 @@ import java.util.Set;
         description = "Gets metadata about flows.",
         authorizations = { @Authorization("Authorization") }
 )
-public class FlowResource extends ApplicationResource {
+public class FlowResource extends AuthorizableApplicationResource {
 
     private final RegistryService registryService;
+    private final LinkService linkService;
+    private final PermissionsService permissionsService;
 
     @Autowired
-    public FlowResource(final RegistryService registryService) {
+    public FlowResource(final RegistryService registryService,
+                        final LinkService linkService,
+                        final PermissionsService permissionsService,
+                        final AuthorizationService authorizationService) {
+        super(authorizationService);
         this.registryService = registryService;
+        this.linkService = linkService;
+        this.permissionsService = permissionsService;
     }
 
     @GET
@@ -62,4 +86,223 @@ public class FlowResource extends ApplicationResource {
         return Response.status(Response.Status.OK).entity(fields).build();
     }
 
+    @GET
+    @Path("{flowId}")
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Gets a flow",
+            response = VersionedFlow.class,
+            extensions = {
+                    @Extension(name = "access-policy", properties = {
+                            @ExtensionProperty(name = "action", value = 
"read"),
+                            @ExtensionProperty(name = "resource", value = 
"/buckets/{bucketId}") })
+            }
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getFlow(
+            @PathParam("flowId")
+            @ApiParam("The flow identifier")
+            final String flowId) {
+
+        final VersionedFlow flow = registryService.getFlow(flowId);
+
+        // this should never happen, but if somehow the back-end didn't 
populate the bucket id let's make sure the flow isn't returned
+        if (StringUtils.isBlank(flow.getBucketIdentifier())) {
+            throw new IllegalStateException("Unable to authorize access 
because bucket identifier is null or blank");
+        }
+
+        authorizeBucketAccess(RequestAction.READ, flow.getBucketIdentifier());
+
+        permissionsService.populateItemPermissions(flow);
+        linkService.populateFlowLinks(flow);
+
+        return Response.status(Response.Status.OK).entity(flow).build();
+    }
+
+    @GET
+    @Path("{flowId}/versions")
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Gets summary information for all versions of a flow. 
Versions are ordered newest->oldest.",
+            response = VersionedFlowSnapshotMetadata.class,
+            responseContainer = "List",
+            extensions = {
+                    @Extension(name = "access-policy", properties = {
+                            @ExtensionProperty(name = "action", value = 
"read"),
+                            @ExtensionProperty(name = "resource", value = 
"/buckets/{bucketId}") })
+            }
+    )
+    @ApiResponses({
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getFlowVersions(
+            @PathParam("flowId")
+            @ApiParam("The flow identifier")
+            final String flowId) {
+
+        final VersionedFlow flow = registryService.getFlow(flowId);
+
+        final String bucketId = flow.getBucketIdentifier();
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalStateException("Unable to authorize access 
because bucket identifier is null or blank");
+        }
+
+        authorizeBucketAccess(RequestAction.READ, bucketId);
+
+        final SortedSet<VersionedFlowSnapshotMetadata> snapshots = 
registryService.getFlowSnapshots(bucketId, flowId);
+        if (snapshots != null ) {
+            linkService.populateSnapshotLinks(snapshots);
+        }
+
+        return Response.status(Response.Status.OK).entity(snapshots).build();
+    }
+
+    @GET
+    @Path("{flowId}/versions/{versionNumber: \\d+}")
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Gets the given version of a flow",
+            response = VersionedFlowSnapshot.class,
+            extensions = {
+                    @Extension(name = "access-policy", properties = {
+                            @ExtensionProperty(name = "action", value = 
"read"),
+                            @ExtensionProperty(name = "resource", value = 
"/buckets/{bucketId}") })
+            }
+    )
+    @ApiResponses({
+            @ApiResponse(code = 400, message = HttpStatusMessages.MESSAGE_400),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getFlowVersion(
+            @PathParam("flowId")
+            @ApiParam("The flow identifier")
+            final String flowId,
+            @PathParam("versionNumber")
+            @ApiParam("The version number")
+            final Integer versionNumber) {
+
+        final VersionedFlowSnapshotMetadata latestMetadata = 
registryService.getLatestFlowSnapshotMetadata(flowId);
+
+        final String bucketId = latestMetadata.getBucketIdentifier();
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalStateException("Unable to authorize access 
because bucket identifier is null or blank");
+        }
+
+        authorizeBucketAccess(RequestAction.READ, bucketId);
+
+        final VersionedFlowSnapshot snapshot = 
registryService.getFlowSnapshot(bucketId, flowId, versionNumber);
+        populateLinksAndPermissions(snapshot);
+        return Response.status(Response.Status.OK).entity(snapshot).build();
+    }
+
+    @GET
+    @Path("{flowId}/versions/latest")
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Get the latest version of a flow",
+            response = VersionedFlowSnapshot.class,
+            extensions = {
+                    @Extension(name = "access-policy", properties = {
+                            @ExtensionProperty(name = "action", value = 
"read"),
+                            @ExtensionProperty(name = "resource", value = 
"/buckets/{bucketId}") })
+            }
+    )
+    @ApiResponses({
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getLatestFlowVersion(
+            @PathParam("flowId")
+            @ApiParam("The flow identifier")
+            final String flowId) {
+
+        final VersionedFlowSnapshotMetadata latestMetadata = 
registryService.getLatestFlowSnapshotMetadata(flowId);
+
+        final String bucketId = latestMetadata.getBucketIdentifier();
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalStateException("Unable to authorize access 
because bucket identifier is null or blank");
+        }
+
+        authorizeBucketAccess(RequestAction.READ, bucketId);
+
+        final VersionedFlowSnapshot lastSnapshot = 
registryService.getFlowSnapshot(bucketId, flowId, latestMetadata.getVersion());
+        populateLinksAndPermissions(lastSnapshot);
+
+        return 
Response.status(Response.Status.OK).entity(lastSnapshot).build();
+    }
+
+    @GET
+    @Path("{flowId}/versions/latest/metadata")
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Get the metadata for the latest version of a flow",
+            response = VersionedFlowSnapshotMetadata.class,
+            extensions = {
+                    @Extension(name = "access-policy", properties = {
+                            @ExtensionProperty(name = "action", value = 
"read"),
+                            @ExtensionProperty(name = "resource", value = 
"/buckets/{bucketId}") })
+            }
+    )
+    @ApiResponses({
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 403, message = HttpStatusMessages.MESSAGE_403),
+            @ApiResponse(code = 404, message = HttpStatusMessages.MESSAGE_404),
+            @ApiResponse(code = 409, message = HttpStatusMessages.MESSAGE_409) 
})
+    public Response getLatestFlowVersionMetadata(
+            @PathParam("flowId")
+            @ApiParam("The flow identifier")
+            final String flowId) {
+
+        final VersionedFlowSnapshotMetadata latestMetadata = 
registryService.getLatestFlowSnapshotMetadata(flowId);
+
+        final String bucketId = latestMetadata.getBucketIdentifier();
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalStateException("Unable to authorize access 
because bucket identifier is null or blank");
+        }
+
+        authorizeBucketAccess(RequestAction.READ, bucketId);
+
+        linkService.populateSnapshotLinks(latestMetadata);
+        return 
Response.status(Response.Status.OK).entity(latestMetadata).build();
+    }
+
+    // override the base implementation so we can provide a different error 
message that doesn't include the bucket id
+    protected void authorizeBucketAccess(RequestAction action, String 
bucketId) {
+        try {
+            super.authorizeBucketAccess(RequestAction.READ, bucketId);
+        } catch (AccessDeniedException e) {
+            throw new AccessDeniedException("User not authorized to view the 
specified flow.", e);
+        }
+    }
+
+    private void populateLinksAndPermissions(VersionedFlowSnapshot snapshot) {
+        if (snapshot.getSnapshotMetadata() != null) {
+            linkService.populateSnapshotLinks(snapshot.getSnapshotMetadata());
+        }
+
+        if (snapshot.getFlow() != null) {
+            linkService.populateFlowLinks(snapshot.getFlow());
+        }
+
+        if (snapshot.getBucket() != null) {
+            permissionsService.populateBucketPermissions(snapshot.getBucket());
+            linkService.populateBucketLinks(snapshot.getBucket());
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
 
b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
index 06186f3..cb14b90 100644
--- 
a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
+++ 
b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureNiFiRegistryClientIT.java
@@ -44,6 +44,7 @@ import org.springframework.context.annotation.Import;
 import org.springframework.test.context.jdbc.Sql;
 import org.springframework.test.context.junit4.SpringRunner;
 
+import javax.ws.rs.ForbiddenException;
 import java.io.IOException;
 import java.util.List;
 
@@ -53,7 +54,7 @@ import java.util.List;
         webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
         properties = "spring.profiles.include=ITSecureFile")
 @Import(SecureITClientConfiguration.class)
-@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = 
"classpath:db/clearDB.sql")
+@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = 
{"classpath:db/clearDB.sql", "classpath:db/FlowsIT.sql"})
 public class SecureNiFiRegistryClientIT extends IntegrationTestBase {
 
     static final Logger LOGGER = 
LoggerFactory.getLogger(SecureNiFiRegistryClientIT.class);
@@ -102,7 +103,7 @@ public class SecureNiFiRegistryClientIT extends 
IntegrationTestBase {
     @Test
     public void testCrudOperations() throws IOException, NiFiRegistryException 
{
         final Bucket bucket = new Bucket();
-        bucket.setName("Bucket 1");
+        bucket.setName("Bucket 1 " + System.currentTimeMillis());
         bucket.setDescription("This is bucket 1");
 
         final BucketClient bucketClient = client.getBucketClient();
@@ -111,11 +112,11 @@ public class SecureNiFiRegistryClientIT extends 
IntegrationTestBase {
         Assert.assertNotNull(createdBucket.getIdentifier());
 
         final List<Bucket> buckets = bucketClient.getAll();
-        Assert.assertEquals(1, buckets.size());
+        Assert.assertEquals(4, buckets.size());
 
         final VersionedFlow flow = new VersionedFlow();
         flow.setBucketIdentifier(createdBucket.getIdentifier());
-        flow.setName("Flow 1");
+        flow.setName("Flow 1 - " + System.currentTimeMillis());
 
         final FlowClient flowClient = client.getFlowClient();
         final VersionedFlow createdFlow = flowClient.create(flow);
@@ -168,4 +169,49 @@ public class SecureNiFiRegistryClientIT extends 
IntegrationTestBase {
         }
     }
 
+    @Test
+    public void testDirectFlowAccess() throws IOException {
+        // this user shouldn't have access to anything
+        final String proxiedEntity = "CN=no-access, OU=nifi";
+
+        final FlowClient proxiedFlowClient = 
client.getFlowClient(proxiedEntity);
+        final FlowSnapshotClient proxiedFlowSnapshotClient = 
client.getFlowSnapshotClient(proxiedEntity);
+
+        try {
+            proxiedFlowClient.get("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.getLatest("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.getLatestMetadata("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.get("1", 1);
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+        try {
+            proxiedFlowSnapshotClient.getSnapshotMetadata("1");
+            Assert.fail("Shouldn't have been able to retrieve flow");
+        } catch (NiFiRegistryException e) {
+            Assert.assertTrue(e.getCause()  instanceof ForbiddenException);
+        }
+
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
 
b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
index d093943..2410234 100644
--- 
a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
+++ 
b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/UnsecuredNiFiRegistryClientIT.java
@@ -173,6 +173,12 @@ public class UnsecuredNiFiRegistryClientIT extends 
UnsecuredITBase {
         Assert.assertNotNull(retrievedFlow2);
         LOGGER.info("Retrieved flow # 2 with id " + 
retrievedFlow2.getIdentifier());
 
+        // get flow without bucket
+        final VersionedFlow retrievedFlow1WithoutBucket = 
flowClient.get(flow1.getIdentifier());
+        Assert.assertNotNull(retrievedFlow1WithoutBucket);
+        Assert.assertEquals(flow1.getIdentifier(), 
retrievedFlow1WithoutBucket.getIdentifier());
+        LOGGER.info("Retrieved flow # 1 without bucket id, with id " + 
retrievedFlow1WithoutBucket.getIdentifier());
+
         // update flows
         final VersionedFlow flow1Update = new VersionedFlow();
         flow1Update.setIdentifier(flow1.getIdentifier());
@@ -218,6 +224,14 @@ public class UnsecuredNiFiRegistryClientIT extends 
UnsecuredITBase {
         Assert.assertTrue(retrievedSnapshot2.isLatest());
         LOGGER.info("Retrieved snapshot # 2 with version " + 
retrievedSnapshot2.getSnapshotMetadata().getVersion());
 
+        // get snapshot without bucket
+        final VersionedFlowSnapshot retrievedSnapshot1WithoutBucket = 
snapshotClient.get(snapshotFlow.getIdentifier(), 1);
+        Assert.assertNotNull(retrievedSnapshot1WithoutBucket);
+        Assert.assertFalse(retrievedSnapshot1WithoutBucket.isLatest());
+        Assert.assertEquals(snapshotFlow.getIdentifier(), 
retrievedSnapshot1WithoutBucket.getSnapshotMetadata().getFlowIdentifier());
+        Assert.assertEquals(1, 
retrievedSnapshot1WithoutBucket.getSnapshotMetadata().getVersion());
+        LOGGER.info("Retrieved snapshot # 1 without using bucket id, with 
version " + retrievedSnapshot1WithoutBucket.getSnapshotMetadata().getVersion());
+
         // get latest
         final VersionedFlowSnapshot retrievedSnapshotLatest = 
snapshotClient.getLatest(snapshotFlow.getBucketIdentifier(), 
snapshotFlow.getIdentifier());
         Assert.assertNotNull(retrievedSnapshotLatest);
@@ -225,6 +239,13 @@ public class UnsecuredNiFiRegistryClientIT extends 
UnsecuredITBase {
         Assert.assertTrue(retrievedSnapshotLatest.isLatest());
         LOGGER.info("Retrieved latest snapshot with version " + 
retrievedSnapshotLatest.getSnapshotMetadata().getVersion());
 
+        // get latest without bucket
+        final VersionedFlowSnapshot retrievedSnapshotLatestWithoutBucket = 
snapshotClient.getLatest(snapshotFlow.getIdentifier());
+        Assert.assertNotNull(retrievedSnapshotLatestWithoutBucket);
+        Assert.assertEquals(snapshot2.getSnapshotMetadata().getVersion(), 
retrievedSnapshotLatestWithoutBucket.getSnapshotMetadata().getVersion());
+        Assert.assertTrue(retrievedSnapshotLatestWithoutBucket.isLatest());
+        LOGGER.info("Retrieved latest snapshot without bucket, with version " 
+ retrievedSnapshotLatestWithoutBucket.getSnapshotMetadata().getVersion());
+
         // get metadata
         final List<VersionedFlowSnapshotMetadata> retrievedMetadata = 
snapshotClient.getSnapshotMetadata(snapshotFlow.getBucketIdentifier(), 
snapshotFlow.getIdentifier());
         Assert.assertNotNull(retrievedMetadata);
@@ -233,6 +254,14 @@ public class UnsecuredNiFiRegistryClientIT extends 
UnsecuredITBase {
         Assert.assertEquals(1, retrievedMetadata.get(1).getVersion());
         retrievedMetadata.stream().forEach(s -> LOGGER.info("Retrieved 
snapshot metadata " + s.getVersion()));
 
+        // get metadata without bucket
+        final List<VersionedFlowSnapshotMetadata> 
retrievedMetadataWithoutBucket = 
snapshotClient.getSnapshotMetadata(snapshotFlow.getIdentifier());
+        Assert.assertNotNull(retrievedMetadataWithoutBucket);
+        Assert.assertEquals(2, retrievedMetadataWithoutBucket.size());
+        Assert.assertEquals(2, 
retrievedMetadataWithoutBucket.get(0).getVersion());
+        Assert.assertEquals(1, 
retrievedMetadataWithoutBucket.get(1).getVersion());
+        retrievedMetadataWithoutBucket.stream().forEach(s -> 
LOGGER.info("Retrieved snapshot metadata " + s.getVersion()));
+
         // get latest metadata
         final VersionedFlowSnapshotMetadata latestMetadata = 
snapshotClient.getLatestMetadata(snapshotFlow.getBucketIdentifier(), 
snapshotFlow.getIdentifier());
         Assert.assertNotNull(latestMetadata);
@@ -246,6 +275,12 @@ public class UnsecuredNiFiRegistryClientIT extends 
UnsecuredITBase {
             Assert.assertEquals("Error retrieving latest snapshot metadata: 
The specified flow ID does not exist in this bucket.", nfe.getMessage());
         }
 
+        // get latest metadata without bucket
+        final VersionedFlowSnapshotMetadata latestMetadataWithoutBucket = 
snapshotClient.getLatestMetadata(snapshotFlow.getIdentifier());
+        Assert.assertNotNull(latestMetadataWithoutBucket);
+        Assert.assertEquals(snapshotFlow.getIdentifier(), 
latestMetadataWithoutBucket.getFlowIdentifier());
+        Assert.assertEquals(2, latestMetadataWithoutBucket.getVersion());
+
         // ---------------------- TEST ITEMS --------------------------//
 
         final ItemsClient itemsClient = client.getItemsClient();

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/5e918f77/nifi-registry-web-api/src/test/resources/conf/secure-file/authorizers.xml
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/test/resources/conf/secure-file/authorizers.xml 
b/nifi-registry-web-api/src/test/resources/conf/secure-file/authorizers.xml
index 97a0bb2..40911b2 100644
--- a/nifi-registry-web-api/src/test/resources/conf/secure-file/authorizers.xml
+++ b/nifi-registry-web-api/src/test/resources/conf/secure-file/authorizers.xml
@@ -47,6 +47,7 @@
         
<class>org.apache.nifi.registry.security.authorization.file.FileUserGroupProvider</class>
         <property name="Users 
File">./target/test-classes/conf/secure-file/users.xml</property>
         <property name="Initial User Identity 1">CN=user1, OU=nifi</property>
+        <property name="Initial User Identity 2">CN=no-access, 
OU=nifi</property>
     </userGroupProvider>
 
     <!--

Reply via email to