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

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

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
new file mode 100644
index 0000000..6dd72e9
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowClient.java
@@ -0,0 +1,119 @@
+/*
+ * 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.client;
+
+import org.apache.nifi.registry.diff.VersionedFlowDifference;
+import org.apache.nifi.registry.field.Fields;
+import org.apache.nifi.registry.flow.VersionedFlow;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Client for interacting with flows.
+ */
+public interface FlowClient {
+
+    /**
+     * Create the given flow in the given bucket.
+     *
+     * @param flow the flow to create
+     * @return the created flow with the identifier populated
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlow create(VersionedFlow flow) throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets the flow with the given id in the given bucket.
+     *
+     * The list of snapshot metadata will NOT be populated.
+     *
+     * @param bucketId a bucket id
+     * @param flowId a flow id
+     * @return the flow with the given id in the given bucket
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlow get(String bucketId, String flowId) throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets the flow with the given id.
+     *
+     * @param flowId a flow id
+     * @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 get(String flowId) throws NiFiRegistryException, IOException;
+
+    /**
+     * Updates the given flow with in the given bucket.
+     *
+     * The identifier of the flow must be populated in the flow object, and only the name and description can be updated.
+     *
+     * @param bucketId a bucket id
+     * @param flow the flow with updates
+     * @return the updated flow
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlow update(String bucketId, VersionedFlow flow) throws NiFiRegistryException, IOException;
+
+    /**
+     *  Deletes the flow with the given id in the given bucket.
+     *
+     * @param bucketId a bucket id
+     * @param flowId the id of the flow to delete
+     * @return the deleted flow
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlow delete(String bucketId, String flowId) throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets the field info for flows.
+     *
+     * @return field info for flows
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    Fields getFields() throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets the flows for a given bucket.
+     *
+     * @param bucketId a bucket id
+     * @return the flows in the given bucket
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    List<VersionedFlow> getByBucket(String bucketId) throws NiFiRegistryException, IOException;
+
+    /**
+     *
+     * @param bucketId a bucket id
+     * @param flowId the flow that is under inspection
+     * @param versionA the first version to use in the comparison
+     * @param versionB the second flow to use in the comparison
+     * @return the list of differences between the 2 flow versions grouped by component
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlowDifference diff(final String bucketId, final String flowId,
+                                 final Integer versionA, final Integer versionB) throws NiFiRegistryException, IOException;
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
new file mode 100644
index 0000000..edf7beb
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/FlowSnapshotClient.java
@@ -0,0 +1,133 @@
+/*
+ * 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.client;
+
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Client for interacting with snapshots.
+ */
+public interface FlowSnapshotClient {
+
+    /**
+     * Creates a new snapshot/version for the given flow.
+     *
+     * The snapshot object must have the version populated, and will receive an error if the submitted version is
+     * not the next one-up version.
+     *
+     * @param snapshot the new snapshot
+     * @return the created snapshot
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    VersionedFlowSnapshot create(VersionedFlowSnapshot snapshot) throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets the snapshot for the given bucket, flow, and version.
+     *
+     * @param bucketId the bucket id
+     * @param flowId the flow id
+     * @param version the version
+     * @return the snapshot with the given version of the given flow in the given bucket
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    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
+     * @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 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
+     * @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 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.
+     *
+     * @param bucketId the bucket id
+     * @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 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/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/ItemsClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/ItemsClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/ItemsClient.java
new file mode 100644
index 0000000..96fa801
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/ItemsClient.java
@@ -0,0 +1,64 @@
+/*
+ * 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.client;
+
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.field.Fields;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Client for interacting with bucket items.
+ *
+ * Bucket items contain the common fields across anything stored in the registry.
+ *
+ * Each item contains a type field and a link to the URI of the specific item.
+ *
+ * i.e. The link field of a flow item would contain the URI to the specific flow.
+ */
+public interface ItemsClient {
+
+    /**
+     * Gets all bucket items in the registry.
+     *
+     * @return the list of all bucket items
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    List<BucketItem> getAll() throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets all bucket items for the given bucket.
+     *
+     * @param bucketId the bucket id
+     * @return the list of items in the given bucket
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    List<BucketItem> getByBucket(String bucketId) throws NiFiRegistryException, IOException;
+
+    /**
+     * Gets the field info for bucket items.
+     *
+     * @return the list of field info
+     * @throws NiFiRegistryException if an error is encountered other than IOException
+     * @throws IOException if an I/O error is encountered
+     */
+    Fields getFields() throws NiFiRegistryException, IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java
new file mode 100644
index 0000000..07fb817
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClient.java
@@ -0,0 +1,89 @@
+/*
+ * 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.client;
+
+import java.io.Closeable;
+
+/**
+ * A client for interacting with the REST API of a NiFi registry instance.
+ */
+public interface NiFiRegistryClient extends Closeable {
+
+    /**
+     * @return the client for interacting with buckets
+     */
+    BucketClient getBucketClient();
+
+    /**
+     * @return the client for interacting with buckets on behalf of the given proxied entities
+     */
+    BucketClient getBucketClient(String ... proxiedEntity);
+
+    /**
+     * @return the client for interacting with flows
+     */
+    FlowClient getFlowClient();
+
+    /**
+     * @return the client for interacting with flows on behalf of the given proxied entities
+     */
+    FlowClient getFlowClient(String ... proxiedEntity);
+
+    /**
+     * @return the client for interacting with flows/snapshots
+     */
+    FlowSnapshotClient getFlowSnapshotClient();
+
+    /**
+     * @return the client for interacting with flows/snapshots on behalf of the given proxied entities
+     */
+    FlowSnapshotClient getFlowSnapshotClient(String ... proxiedEntity);
+
+    /**
+     * @return the client for interacting with bucket items
+     */
+    ItemsClient getItemsClient();
+
+    /**
+     * @return the client for interacting with bucket items on behalf of the given proxied entities
+     */
+    ItemsClient getItemsClient(String ... proxiedEntity);
+
+    /**
+     * @return the client for obtaining information about the current user
+     */
+    UserClient getUserClient();
+
+    /**
+     * @return the client for obtaining information about the current user based on the given proxied entities
+     */
+    UserClient getUserClient(String ... proxiedEntity);
+
+    /**
+     * The builder interface that implementations should provide for obtaining the client.
+     */
+    interface Builder {
+
+        Builder config(NiFiRegistryClientConfig clientConfig);
+
+        NiFiRegistryClientConfig getConfig();
+
+        NiFiRegistryClient build();
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java
new file mode 100644
index 0000000..de77b51
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryClientConfig.java
@@ -0,0 +1,257 @@
+/*
+ * 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.client;
+
+import org.apache.nifi.registry.security.util.KeyStoreUtils;
+import org.apache.nifi.registry.security.util.KeystoreType;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+
+/**
+ * Configuration for a NiFiRegistryClient.
+ */
+public class NiFiRegistryClientConfig {
+
+    private final String baseUrl;
+    private final SSLContext sslContext;
+    private final String keystoreFilename;
+    private final String keystorePass;
+    private final String keyPass;
+    private final KeystoreType keystoreType;
+    private final String truststoreFilename;
+    private final String truststorePass;
+    private final KeystoreType truststoreType;
+    private final HostnameVerifier hostnameVerifier;
+    private final Integer readTimeout;
+    private final Integer connectTimeout;
+
+
+    private NiFiRegistryClientConfig(final Builder builder) {
+        this.baseUrl = builder.baseUrl;
+        this.sslContext = builder.sslContext;
+        this.keystoreFilename = builder.keystoreFilename;
+        this.keystorePass = builder.keystorePass;
+        this.keyPass = builder.keyPass;
+        this.keystoreType = builder.keystoreType;
+        this.truststoreFilename = builder.truststoreFilename;
+        this.truststorePass = builder.truststorePass;
+        this.truststoreType = builder.truststoreType;
+        this.hostnameVerifier = builder.hostnameVerifier;
+        this.readTimeout = builder.readTimeout;
+        this.connectTimeout = builder.connectTimeout;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public SSLContext getSslContext() {
+        if (sslContext != null) {
+            return sslContext;
+        }
+
+        final KeyManagerFactory keyManagerFactory;
+        if (keystoreFilename != null && keystorePass != null && keystoreType != null) {
+            try {
+                // prepare the keystore
+                final KeyStore keyStore = KeyStoreUtils.getKeyStore(keystoreType.name());
+                try (final InputStream keyStoreStream = new FileInputStream(new File(keystoreFilename))) {
+                    keyStore.load(keyStoreStream, keystorePass.toCharArray());
+                }
+                keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+
+                if (keyPass == null) {
+                    keyManagerFactory.init(keyStore, keystorePass.toCharArray());
+                } else {
+                    keyManagerFactory.init(keyStore, keyPass.toCharArray());
+                }
+            } catch (final Exception e) {
+                throw new IllegalStateException("Failed to load Keystore", e);
+            }
+        } else {
+            keyManagerFactory = null;
+        }
+
+        final TrustManagerFactory trustManagerFactory;
+        if (truststoreFilename != null && truststorePass != null && truststoreType != null) {
+            try {
+                // prepare the truststore
+                final KeyStore trustStore = KeyStoreUtils.getTrustStore(truststoreType.name());
+                try (final InputStream trustStoreStream = new FileInputStream(new File(truststoreFilename))) {
+                    trustStore.load(trustStoreStream, truststorePass.toCharArray());
+                }
+                trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+                trustManagerFactory.init(trustStore);
+            } catch (final Exception e) {
+                throw new IllegalStateException("Failed to load Truststore", e);
+            }
+        } else {
+            trustManagerFactory = null;
+        }
+
+        if (keyManagerFactory != null || trustManagerFactory != null) {
+            try {
+                // initialize the ssl context
+                KeyManager[] keyManagers = keyManagerFactory != null ? keyManagerFactory.getKeyManagers() : null;
+                TrustManager[] trustManagers = trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null;
+                final SSLContext sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(keyManagers, trustManagers, new SecureRandom());
+                sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
+
+                return sslContext;
+            } catch (final Exception e) {
+                throw new IllegalStateException("Created keystore and truststore but failed to initialize SSLContext", e);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public String getKeystoreFilename() {
+        return keystoreFilename;
+    }
+
+    public String getKeystorePass() {
+        return keystorePass;
+    }
+
+    public String getKeyPass() {
+        return keyPass;
+    }
+
+    public KeystoreType getKeystoreType() {
+        return keystoreType;
+    }
+
+    public String getTruststoreFilename() {
+        return truststoreFilename;
+    }
+
+    public String getTruststorePass() {
+        return truststorePass;
+    }
+
+    public KeystoreType getTruststoreType() {
+        return truststoreType;
+    }
+
+    public HostnameVerifier getHostnameVerifier() {
+        return hostnameVerifier;
+    }
+
+    public Integer getReadTimeout() {
+        return readTimeout;
+    }
+
+    public Integer getConnectTimeout() {
+        return connectTimeout;
+    }
+
+    /**
+     * Builder for client configuration.
+     */
+    public static class Builder {
+
+        private String baseUrl;
+        private SSLContext sslContext;
+        private String keystoreFilename;
+        private String keystorePass;
+        private String keyPass;
+        private KeystoreType keystoreType;
+        private String truststoreFilename;
+        private String truststorePass;
+        private KeystoreType truststoreType;
+        private HostnameVerifier hostnameVerifier;
+        private Integer readTimeout;
+        private Integer connectTimeout;
+
+        public Builder baseUrl(final String baseUrl) {
+            this.baseUrl = baseUrl;
+            return this;
+        }
+
+        public Builder sslContext(final SSLContext sslContext) {
+            this.sslContext = sslContext;
+            return this;
+        }
+
+        public Builder keystoreFilename(final String keystoreFilename) {
+            this.keystoreFilename = keystoreFilename;
+            return this;
+        }
+
+        public Builder keystorePassword(final String keystorePass) {
+            this.keystorePass = keystorePass;
+            return this;
+        }
+
+        public Builder keyPassword(final String keyPass) {
+            this.keyPass = keyPass;
+            return this;
+        }
+
+        public Builder keystoreType(final KeystoreType keystoreType) {
+            this.keystoreType = keystoreType;
+            return this;
+        }
+
+        public Builder truststoreFilename(final String truststoreFilename) {
+            this.truststoreFilename = truststoreFilename;
+            return this;
+        }
+
+        public Builder truststorePassword(final String truststorePass) {
+            this.truststorePass = truststorePass;
+            return this;
+        }
+
+        public Builder truststoreType(final KeystoreType truststoreType) {
+            this.truststoreType = truststoreType;
+            return this;
+        }
+
+        public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) {
+            this.hostnameVerifier = hostnameVerifier;
+            return this;
+        }
+
+        public Builder readTimeout(final Integer readTimeout) {
+            this.readTimeout = readTimeout;
+            return this;
+        }
+
+        public Builder connectTimeout(final Integer connectTimeout) {
+            this.connectTimeout = connectTimeout;
+            return this;
+        }
+
+        public NiFiRegistryClientConfig build() {
+            return new NiFiRegistryClientConfig(this);
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryException.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryException.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryException.java
new file mode 100644
index 0000000..273a032
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/NiFiRegistryException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.client;
+
+/**
+ * Indicates an error interacting with the NiFi registry for a reason other than IOException.
+ */
+public class NiFiRegistryException extends Exception {
+
+    public NiFiRegistryException(final String message) {
+        super(message);
+    }
+
+    public NiFiRegistryException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/UserClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/UserClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/UserClient.java
new file mode 100644
index 0000000..181f7af
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/UserClient.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.client;
+
+import org.apache.nifi.registry.authorization.CurrentUser;
+
+import java.io.IOException;
+
+public interface UserClient {
+
+    /**
+     * Obtains the access status of the current user.
+     *
+     * If the UserClient was obtained with proxied entities, then the access status should represent the status
+     * of the last identity in the chain.
+     *
+     * If the UserClient was obtained without proxied entities, then it would represent the identity of the certificate
+     * in the keystore used by the client.
+     *
+     * If the registry is not in secure mode, the anonymous identity is expected to be returned along with a flag indicating
+     * the user is anonymous.
+     *
+     * @return the access status of the current user
+     * @throws NiFiRegistryException if the proxying user is not a valid proxy or identity claim is otherwise invalid
+     */
+    CurrentUser getAccessStatus() throws NiFiRegistryException, IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java
new file mode 100644
index 0000000..479699e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/AbstractJerseyClient.java
@@ -0,0 +1,120 @@
+/*
+ * 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.client.impl;
+
+import org.apache.nifi.registry.client.NiFiRegistryException;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for the client operations to share exception handling.
+ *
+ * Sub-classes should always execute a request from getRequestBuilder(target) to ensure proper headers are sent.
+ */
+public class AbstractJerseyClient {
+
+    private final Map<String,String> headers;
+
+    public AbstractJerseyClient(final Map<String, String> headers) {
+        this.headers = headers == null ? Collections.emptyMap() : Collections.unmodifiableMap(new HashMap<>(headers));
+    }
+
+    protected Map<String,String> getHeaders() {
+        return headers;
+    }
+
+    /**
+     * Creates a new Invocation.Builder for the given WebTarget with the headers added to the builder.
+     *
+     * @param webTarget the target for the request
+     * @return the builder for the target with the headers added
+     */
+    protected Invocation.Builder getRequestBuilder(final WebTarget webTarget) {
+        final Invocation.Builder requestBuilder = webTarget.request();
+        headers.entrySet().stream().forEach(e -> requestBuilder.header(e.getKey(), e.getValue()));
+        return requestBuilder;
+    }
+
+    /**
+     * Executes the given action and returns the result.
+     *
+     * @param action the action to execute
+     * @param errorMessage the message to use if a NiFiRegistryException is thrown
+     * @param <T> the return type of the action
+     * @return the result of the action
+     * @throws NiFiRegistryException if any exception other than IOException is encountered
+     * @throws IOException if an I/O error occurs communicating with the registry
+     */
+    protected <T> T executeAction(final String errorMessage, final NiFiRegistryAction<T> action) throws NiFiRegistryException, IOException {
+        try {
+            return action.execute();
+        } catch (final Exception e) {
+            final Throwable ioeCause = getIOExceptionCause(e);
+
+            if (ioeCause == null) {
+                final StringBuilder errorMessageBuilder = new StringBuilder(errorMessage);
+
+                // see if we have a WebApplicationException, and if so add the response body to the error message
+                if (e instanceof WebApplicationException) {
+                    final Response response = ((WebApplicationException) e).getResponse();
+                    final String responseBody = response.readEntity(String.class);
+                    errorMessageBuilder.append(": ").append(responseBody);
+                }
+
+                throw new NiFiRegistryException(errorMessageBuilder.toString(), e);
+            } else {
+                throw (IOException) ioeCause;
+            }
+        }
+    }
+
+
+    /**
+     * An action to execute with the given return type.
+     *
+     * @param <T> the return type of the action
+     */
+    protected interface NiFiRegistryAction<T> {
+
+        T execute();
+
+    }
+
+    /**
+     * @param e an exception that was encountered interacting with the registry
+     * @return the IOException that caused this exception, or null if the an IOException did not cause this exception
+     */
+    protected Throwable getIOExceptionCause(final Throwable e) {
+        if (e == null) {
+            return null;
+        }
+
+        if (e instanceof IOException) {
+            return e;
+        }
+
+        return getIOExceptionCause(e.getCause());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/BucketItemDeserializer.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/BucketItemDeserializer.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/BucketItemDeserializer.java
new file mode 100644
index 0000000..5640d43
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/BucketItemDeserializer.java
@@ -0,0 +1,76 @@
+/*
+ * 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.client.impl;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.bucket.BucketItemType;
+import org.apache.nifi.registry.flow.VersionedFlow;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class BucketItemDeserializer extends StdDeserializer<BucketItem[]> {
+
+    public BucketItemDeserializer() {
+        super(BucketItem[].class);
+    }
+
+    @Override
+    public BucketItem[] deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
+        final JsonNode arrayNode = jsonParser.getCodec().readTree(jsonParser);
+
+        final List<BucketItem> bucketItems = new ArrayList<>();
+
+        final Iterator<JsonNode> nodeIter = arrayNode.elements();
+        while (nodeIter.hasNext()) {
+            final JsonNode node = nodeIter.next();
+
+            final String type = node.get("type").asText();
+            if (StringUtils.isBlank(type)) {
+                throw new IllegalStateException("BucketItem type cannot be null or blank");
+            }
+
+            final BucketItemType bucketItemType;
+            try {
+                bucketItemType = BucketItemType.valueOf(type);
+            } catch (Exception e) {
+                throw new IllegalStateException("Unknown type for BucketItem: " + type, e);
+            }
+
+
+            switch (bucketItemType) {
+                case Flow:
+                    final VersionedFlow versionedFlow = jsonParser.getCodec().treeToValue(node, VersionedFlow.class);
+                    bucketItems.add(versionedFlow);
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown type for BucketItem");
+            }
+        }
+
+        return bucketItems.toArray(new BucketItem[bucketItems.size()]);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java
new file mode 100644
index 0000000..f84f8c6
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyBucketClient.java
@@ -0,0 +1,140 @@
+/*
+ * 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.client.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.bucket.Bucket;
+import org.apache.nifi.registry.client.BucketClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.field.Fields;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Jersey implementation of BucketClient.
+ */
+public class JerseyBucketClient extends AbstractJerseyClient implements BucketClient {
+
+    private final WebTarget bucketsTarget;
+
+
+    public JerseyBucketClient(final WebTarget baseTarget) {
+        this(baseTarget, Collections.emptyMap());
+    }
+
+    public JerseyBucketClient(final WebTarget baseTarget, final Map<String,String> headers) {
+        super(headers);
+        this.bucketsTarget = baseTarget.path("/buckets");
+    }
+
+    @Override
+    public Bucket create(final Bucket bucket) throws NiFiRegistryException, IOException {
+        if (bucket == null) {
+            throw new IllegalArgumentException("Bucket cannot be null");
+        }
+
+        return executeAction("Error creating bucket", () -> {
+            return getRequestBuilder(bucketsTarget)
+                    .post(
+                            Entity.entity(bucket, MediaType.APPLICATION_JSON),
+                            Bucket.class
+                    );
+        });
+
+    }
+
+    @Override
+    public Bucket get(final String bucketId) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket ID cannot be blank");
+        }
+
+        return executeAction("Error retrieving bucket", () -> {
+            final WebTarget target = bucketsTarget
+                    .path("/{bucketId}")
+                    .resolveTemplate("bucketId", bucketId);
+
+            return getRequestBuilder(target).get(Bucket.class);
+        });
+
+    }
+
+    @Override
+    public Bucket update(final Bucket bucket) throws NiFiRegistryException, IOException {
+        if (bucket == null) {
+            throw new IllegalArgumentException("Bucket cannot be null");
+        }
+
+        if (StringUtils.isBlank(bucket.getIdentifier())) {
+            throw new IllegalArgumentException("Bucket Identifier must be provided");
+        }
+
+        return executeAction("Error updating bucket", () -> {
+            final WebTarget target = bucketsTarget
+                    .path("/{bucketId}")
+                    .resolveTemplate("bucketId", bucket.getIdentifier());
+
+            return getRequestBuilder(target)
+                    .put(
+                            Entity.entity(bucket, MediaType.APPLICATION_JSON),
+                            Bucket.class
+                    );
+
+        });
+    }
+
+    @Override
+    public Bucket delete(final String bucketId) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket ID cannot be blank");
+        }
+
+        return executeAction("Error deleting bucket", () -> {
+            final WebTarget target = bucketsTarget
+                    .path("/{bucketId}")
+                    .resolveTemplate("bucketId", bucketId);
+
+            return getRequestBuilder(target).delete(Bucket.class);
+        });
+    }
+
+    @Override
+    public Fields getFields() throws NiFiRegistryException, IOException {
+        return executeAction("Error retrieving bucket field info", () -> {
+            final WebTarget target = bucketsTarget
+                    .path("/fields");
+
+            return getRequestBuilder(target).get(Fields.class);
+        });
+    }
+
+    @Override
+    public List<Bucket> getAll() throws NiFiRegistryException, IOException {
+        return executeAction("Error retrieving all buckets", () -> {
+            final Bucket[] buckets = getRequestBuilder(bucketsTarget).get(Bucket[].class);
+            return buckets == null ? Collections.emptyList() : Arrays.asList(buckets);
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
new file mode 100644
index 0000000..486a20a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowClient.java
@@ -0,0 +1,205 @@
+/*
+ * 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.client.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.client.FlowClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.diff.VersionedFlowDifference;
+import org.apache.nifi.registry.field.Fields;
+import org.apache.nifi.registry.flow.VersionedFlow;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Jersey implementation of FlowClient.
+ */
+public class JerseyFlowClient extends AbstractJerseyClient  implements FlowClient {
+
+    private final WebTarget flowsTarget;
+    private final WebTarget bucketFlowsTarget;
+
+    public JerseyFlowClient(final WebTarget baseTarget) {
+        this(baseTarget, Collections.emptyMap());
+    }
+
+    public JerseyFlowClient(final WebTarget baseTarget, final Map<String,String> headers) {
+        super(headers);
+        this.flowsTarget = baseTarget.path("/flows");
+        this.bucketFlowsTarget = baseTarget.path("/buckets/{bucketId}/flows");
+    }
+
+    @Override
+    public VersionedFlow create(final VersionedFlow flow) throws NiFiRegistryException, IOException {
+        if (flow == null) {
+            throw new IllegalArgumentException("VersionedFlow cannot be null");
+        }
+
+        final String bucketId = flow.getBucketIdentifier();
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        return executeAction("Error creating flow", () -> {
+            final WebTarget target = bucketFlowsTarget
+                    .resolveTemplate("bucketId", bucketId);
+
+            return getRequestBuilder(target)
+                    .post(
+                            Entity.entity(flow, MediaType.APPLICATION_JSON),
+                            VersionedFlow.class
+                    );
+        });
+    }
+
+    @Override
+    public VersionedFlow get(final String bucketId, final String flowId) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error retrieving flow", () -> {
+            final WebTarget target = bucketFlowsTarget
+                    .path("/{flowId}")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId);
+
+            return  getRequestBuilder(target).get(VersionedFlow.class);
+        });
+    }
+
+    @Override
+    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 = flowsTarget
+                    .path("/{flowId}")
+                    .resolveTemplate("flowId", flowId);
+
+            return  getRequestBuilder(target).get(VersionedFlow.class);
+        });
+    }
+
+    @Override
+    public VersionedFlow update(final String bucketId, final VersionedFlow flow) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        if (flow == null) {
+            throw new IllegalArgumentException("VersionedFlow cannot be null");
+        }
+
+        if (StringUtils.isBlank(flow.getIdentifier())) {
+            throw new IllegalArgumentException("VersionedFlow identifier must be provided");
+        }
+
+        return executeAction("Error updating flow", () -> {
+            final WebTarget target = bucketFlowsTarget
+                    .path("/{flowId}")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flow.getIdentifier());
+
+            return  getRequestBuilder(target)
+                    .put(
+                            Entity.entity(flow, MediaType.APPLICATION_JSON),
+                            VersionedFlow.class
+                    );
+        });
+    }
+
+    @Override
+    public VersionedFlow delete(final String bucketId, final String flowId) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error deleting flow", () -> {
+            final WebTarget target = bucketFlowsTarget
+                    .path("/{flowId}")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId);
+
+            return getRequestBuilder(target).delete(VersionedFlow.class);
+        });
+    }
+
+    @Override
+    public Fields getFields() throws NiFiRegistryException, IOException {
+        return executeAction("Error retrieving fields info for flows", () -> {
+            final WebTarget target = flowsTarget.path("/fields");
+            return getRequestBuilder(target).get(Fields.class);
+        });
+    }
+
+    @Override
+    public List<VersionedFlow> getByBucket(final String bucketId) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        return executeAction("Error getting flows for bucket", () -> {
+            WebTarget target = bucketFlowsTarget;
+            target = target.resolveTemplate("bucketId", bucketId);
+
+            final VersionedFlow[] versionedFlows = getRequestBuilder(target).get(VersionedFlow[].class);
+            return  versionedFlows == null ? Collections.emptyList() : Arrays.asList(versionedFlows);
+        });
+    }
+
+    @Override
+    public VersionedFlowDifference diff(final String bucketId, final String flowId,
+                                        final Integer versionA, final Integer versionB) throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error retrieving flow", () -> {
+            final WebTarget target = bucketFlowsTarget
+                    .path("/{flowId}/diff/{versionA}/{versionB}")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId)
+                    .resolveTemplate("versionA", versionA)
+                    .resolveTemplate("versionB", versionB);
+
+            return  getRequestBuilder(target).get(VersionedFlowDifference.class);
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
new file mode 100644
index 0000000..befe389
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyFlowSnapshotClient.java
@@ -0,0 +1,246 @@
+/*
+ * 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.client.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.client.FlowSnapshotClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
+import org.apache.nifi.registry.flow.VersionedFlowSnapshotMetadata;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Jersey implementation of FlowSnapshotClient.
+ */
+public class JerseyFlowSnapshotClient extends AbstractJerseyClient implements FlowSnapshotClient {
+
+    final WebTarget bucketFlowSnapshotTarget;
+    final WebTarget flowsFlowSnapshotTarget;
+
+    public JerseyFlowSnapshotClient(final WebTarget baseTarget) {
+        this(baseTarget, Collections.emptyMap());
+    }
+
+    public JerseyFlowSnapshotClient(final WebTarget baseTarget, final Map<String,String> headers) {
+        super(headers);
+        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 {
+        if (snapshot.getSnapshotMetadata() == null) {
+            throw new IllegalArgumentException("Snapshot Metadata cannot be null");
+        }
+
+        final String bucketId = snapshot.getSnapshotMetadata().getBucketIdentifier();
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        final String flowId = snapshot.getSnapshotMetadata().getFlowIdentifier();
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error creating snapshot", () -> {
+            final WebTarget target = bucketFlowSnapshotTarget
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId);
+
+            return  getRequestBuilder(target)
+                    .post(
+                            Entity.entity(snapshot, MediaType.APPLICATION_JSON),
+                            VersionedFlowSnapshot.class
+                    );
+        });
+    }
+
+    @Override
+    public VersionedFlowSnapshot get(final String bucketId, final String flowId, final int version)
+            throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        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 = bucketFlowSnapshotTarget
+                    .path("/{version}")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId)
+                    .resolveTemplate("version", version);
+
+            return getRequestBuilder(target).get(VersionedFlowSnapshot.class);
+        });
+    }
+
+    @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)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error retrieving latest snapshot", () -> {
+            final WebTarget target = bucketFlowSnapshotTarget
+                    .path("/latest")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId);
+
+            return getRequestBuilder(target).get(VersionedFlowSnapshot.class);
+        });
+    }
+
+    @Override
+    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");
+        }
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error retrieving latest snapshot metadata", () -> {
+            final WebTarget target = bucketFlowSnapshotTarget
+                    .path("/latest/metadata")
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId);
+
+            return getRequestBuilder(target).get(VersionedFlowSnapshotMetadata.class);
+        });
+    }
+
+    @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 {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        if (StringUtils.isBlank(flowId)) {
+            throw new IllegalArgumentException("Flow Identifier cannot be blank");
+        }
+
+        return executeAction("Error retrieving snapshot metadata", () -> {
+            final WebTarget target = bucketFlowSnapshotTarget
+                    .resolveTemplate("bucketId", bucketId)
+                    .resolveTemplate("flowId", flowId);
+
+            final VersionedFlowSnapshotMetadata[] snapshots = getRequestBuilder(target)
+                    .get(VersionedFlowSnapshotMetadata[].class);
+
+            return snapshots == null ? Collections.emptyList() : Arrays.asList(snapshots);
+        });
+    }
+
+    @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/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java
new file mode 100644
index 0000000..6b01fc4
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyItemsClient.java
@@ -0,0 +1,85 @@
+/*
+ * 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.client.impl;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.client.ItemsClient;
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.field.Fields;
+
+import javax.ws.rs.client.WebTarget;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Jersey implementation of ItemsClient.
+ */
+public class JerseyItemsClient extends AbstractJerseyClient implements ItemsClient {
+
+    private final WebTarget itemsTarget;
+
+    public JerseyItemsClient(final WebTarget baseTarget) {
+        this(baseTarget, Collections.emptyMap());
+    }
+
+    public JerseyItemsClient(final WebTarget baseTarget, final Map<String,String> headers) {
+        super(headers);
+        this.itemsTarget = baseTarget.path("/items");
+    }
+
+
+
+    @Override
+    public List<BucketItem> getAll() throws NiFiRegistryException, IOException {
+        return executeAction("", () -> {
+            WebTarget target = itemsTarget;
+            final BucketItem[] bucketItems = getRequestBuilder(target).get(BucketItem[].class);
+            return bucketItems == null ? Collections.emptyList() : Arrays.asList(bucketItems);
+        });
+    }
+
+    @Override
+    public List<BucketItem> getByBucket(final String bucketId)
+            throws NiFiRegistryException, IOException {
+        if (StringUtils.isBlank(bucketId)) {
+            throw new IllegalArgumentException("Bucket Identifier cannot be blank");
+        }
+
+        return executeAction("", () -> {
+            WebTarget target = itemsTarget
+                    .path("/{bucketId}")
+                    .resolveTemplate("bucketId", bucketId);
+
+            final BucketItem[] bucketItems = getRequestBuilder(target).get(BucketItem[].class);
+            return bucketItems == null ? Collections.emptyList() : Arrays.asList(bucketItems);
+        });
+    }
+
+    @Override
+    public Fields getFields() throws NiFiRegistryException, IOException {
+        return executeAction("", () -> {
+            final WebTarget target = itemsTarget.path("/fields");
+            return getRequestBuilder(target).get(Fields.class);
+
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java
new file mode 100644
index 0000000..329a47a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyNiFiRegistryClient.java
@@ -0,0 +1,247 @@
+/*
+ * 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.client.impl;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.client.BucketClient;
+import org.apache.nifi.registry.client.FlowClient;
+import org.apache.nifi.registry.client.FlowSnapshotClient;
+import org.apache.nifi.registry.client.ItemsClient;
+import org.apache.nifi.registry.client.NiFiRegistryClient;
+import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
+import org.apache.nifi.registry.client.UserClient;
+import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * A NiFiRegistryClient that uses Jersey Client.
+ */
+public class JerseyNiFiRegistryClient implements NiFiRegistryClient {
+
+    static final String NIFI_REGISTRY_CONTEXT = "nifi-registry-api";
+    static final int DEFAULT_CONNECT_TIMEOUT = 10000;
+    static final int DEFAULT_READ_TIMEOUT = 10000;
+
+    private final Client client;
+    private final WebTarget baseTarget;
+
+    private final BucketClient bucketClient;
+    private final FlowClient flowClient;
+    private final FlowSnapshotClient flowSnapshotClient;
+    private final ItemsClient itemsClient;
+
+    private JerseyNiFiRegistryClient(final NiFiRegistryClient.Builder builder) {
+        final NiFiRegistryClientConfig registryClientConfig = builder.getConfig();
+        if (registryClientConfig == null) {
+            throw new IllegalArgumentException("NiFiRegistryClientConfig cannot be null");
+        }
+
+        String baseUrl = registryClientConfig.getBaseUrl();
+        if (StringUtils.isBlank(baseUrl)) {
+            throw new IllegalArgumentException("Base URL cannot be blank");
+        }
+
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+
+        if (!baseUrl.endsWith(NIFI_REGISTRY_CONTEXT)) {
+            baseUrl = baseUrl + "/" + NIFI_REGISTRY_CONTEXT;
+        }
+
+        try {
+            new URI(baseUrl);
+        } catch (final Exception e) {
+            throw new IllegalArgumentException("Invalid base URL: " + e.getMessage(), e);
+        }
+
+        final SSLContext sslContext = registryClientConfig.getSslContext();
+        final HostnameVerifier hostnameVerifier = registryClientConfig.getHostnameVerifier();
+
+        final ClientBuilder clientBuilder = ClientBuilder.newBuilder();
+        if (sslContext != null) {
+            clientBuilder.sslContext(sslContext);
+        }
+        if (hostnameVerifier != null) {
+            clientBuilder.hostnameVerifier(hostnameVerifier);
+        }
+
+        final int connectTimeout = registryClientConfig.getConnectTimeout() == null ? DEFAULT_CONNECT_TIMEOUT : registryClientConfig.getConnectTimeout();
+        final int readTimeout = registryClientConfig.getReadTimeout() == null ? DEFAULT_READ_TIMEOUT : registryClientConfig.getReadTimeout();
+
+        final ClientConfig clientConfig = new ClientConfig();
+        clientConfig.property(ClientProperties.CONNECT_TIMEOUT, connectTimeout);
+        clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout);
+        clientConfig.register(jacksonJaxbJsonProvider());
+        clientBuilder.withConfig(clientConfig);
+        this.client = clientBuilder.build();
+
+        this.baseTarget = client.target(baseUrl);
+        this.bucketClient = new JerseyBucketClient(baseTarget);
+        this.flowClient = new JerseyFlowClient(baseTarget);
+        this.flowSnapshotClient = new JerseyFlowSnapshotClient(baseTarget);
+        this.itemsClient = new JerseyItemsClient(baseTarget);
+    }
+
+    @Override
+    public BucketClient getBucketClient() {
+        return this.bucketClient;
+    }
+
+    @Override
+    public FlowClient getFlowClient() {
+        return this.flowClient;
+    }
+
+    @Override
+    public FlowSnapshotClient getFlowSnapshotClient() {
+        return this.flowSnapshotClient;
+    }
+
+    @Override
+    public ItemsClient getItemsClient() {
+        return this.itemsClient;
+    }
+
+    @Override
+    public BucketClient getBucketClient(String... proxiedEntity) {
+        final Map<String,String> headers = getHeaders(proxiedEntity);
+        return new JerseyBucketClient(baseTarget, headers);
+    }
+
+    @Override
+    public FlowClient getFlowClient(String... proxiedEntity) {
+        final Map<String,String> headers = getHeaders(proxiedEntity);
+        return new JerseyFlowClient(baseTarget, headers);
+    }
+
+    @Override
+    public FlowSnapshotClient getFlowSnapshotClient(String... proxiedEntity) {
+        final Map<String,String> headers = getHeaders(proxiedEntity);
+        return new JerseyFlowSnapshotClient(baseTarget, headers);
+    }
+
+    @Override
+    public ItemsClient getItemsClient(String... proxiedEntity) {
+        final Map<String,String> headers = getHeaders(proxiedEntity);
+        return new JerseyItemsClient(baseTarget, headers);
+    }
+
+    @Override
+    public UserClient getUserClient() {
+        return new JerseyUserClient(baseTarget);
+    }
+
+    @Override
+    public UserClient getUserClient(String... proxiedEntity) {
+        final Map<String,String> headers = getHeaders(proxiedEntity);
+        return new JerseyUserClient(baseTarget, headers);
+    }
+
+    private Map<String,String> getHeaders(String[] proxiedEntities) {
+        final String proxiedEntitiesValue = getProxiedEntitesValue(proxiedEntities);
+
+        final Map<String,String> headers = new HashMap<>();
+        if (proxiedEntitiesValue != null) {
+            headers.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesValue);
+        }
+        return headers;
+    }
+
+    private String getProxiedEntitesValue(String[] proxiedEntities) {
+        if (proxiedEntities == null) {
+            return null;
+        }
+
+        final List<String> proxiedEntityChain = Arrays.stream(proxiedEntities).map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
+        return StringUtils.join(proxiedEntityChain, "");
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (this.client != null) {
+            try {
+                this.client.close();
+            } catch (Exception e) {
+
+            }
+        }
+    }
+
+    /**
+     * Builder for creating a JerseyNiFiRegistryClient.
+     */
+    public static class Builder implements NiFiRegistryClient.Builder {
+
+        private NiFiRegistryClientConfig clientConfig;
+
+        @Override
+        public Builder config(final NiFiRegistryClientConfig clientConfig) {
+            this.clientConfig = clientConfig;
+            return this;
+        }
+
+        @Override
+        public NiFiRegistryClientConfig getConfig() {
+            return clientConfig;
+        }
+
+        @Override
+        public NiFiRegistryClient build() {
+            return new JerseyNiFiRegistryClient(this);
+        }
+
+    }
+
+    private static JacksonJaxbJsonProvider jacksonJaxbJsonProvider() {
+        JacksonJaxbJsonProvider jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider();
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
+        mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(mapper.getTypeFactory()));
+        // Ignore unknown properties so that deployed client remain compatible with future versions of NiFi Registry that add new fields
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(BucketItem[].class, new BucketItemDeserializer());
+        mapper.registerModule(module);
+
+        jacksonJaxbJsonProvider.setMapper(mapper);
+        return jacksonJaxbJsonProvider;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java
new file mode 100644
index 0000000..7625f35
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-client/src/main/java/org/apache/nifi/registry/client/impl/JerseyUserClient.java
@@ -0,0 +1,47 @@
+/*
+ * 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.client.impl;
+
+import org.apache.nifi.registry.client.NiFiRegistryException;
+import org.apache.nifi.registry.client.UserClient;
+import org.apache.nifi.registry.authorization.CurrentUser;
+
+import javax.ws.rs.client.WebTarget;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+public class JerseyUserClient extends AbstractJerseyClient implements UserClient {
+
+    private final WebTarget accessTarget;
+
+    public JerseyUserClient(final WebTarget baseTarget) {
+        this(baseTarget, Collections.emptyMap());
+    }
+
+    public JerseyUserClient(final WebTarget baseTarget, final Map<String,String> headers) {
+        super(headers);
+        this.accessTarget = baseTarget.path("/access");
+    }
+
+    @Override
+    public CurrentUser getAccessStatus() throws NiFiRegistryException, IOException {
+        return executeAction("Error retrieving access status for the current user", () -> {
+            return getRequestBuilder(accessTarget).get(CurrentUser.class);
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/pom.xml b/nifi-registry-core/nifi-registry-data-model/pom.xml
new file mode 100644
index 0000000..3b63ddc
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+    license agreements. See the NOTICE file distributed with this work for additional 
+    information regarding copyright ownership. The ASF licenses this file to 
+    You under the Apache License, Version 2.0 (the "License"); you may not use 
+    this file except in compliance with the License. You may obtain a copy of 
+    the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
+    by applicable law or agreed to in writing, software distributed under the 
+    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
+    OF ANY KIND, either express or implied. See the License for the specific 
+    language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi.registry</groupId>
+        <artifactId>nifi-registry-core</artifactId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+    
+    <artifactId>nifi-registry-data-model</artifactId>
+    <packaging>jar</packaging>
+    
+    <dependencies>
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+    </dependencies>
+</project>