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:44 UTC

[45/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-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedConnection.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedConnection.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedConnection.java
new file mode 100644
index 0000000..52b7d70
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedConnection.java
@@ -0,0 +1,177 @@
+/*
+ * 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.flow;
+
+import java.util.List;
+import java.util.Set;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedConnection extends VersionedComponent {
+    private ConnectableComponent source;
+    private ConnectableComponent destination;
+    private Integer labelIndex;
+    private Long zIndex;
+    private Set<String> selectedRelationships;
+
+    private Long backPressureObjectThreshold;
+    private String backPressureDataSizeThreshold;
+    private String flowFileExpiration;
+    private List<String> prioritizers;
+    private List<Position> bends;
+
+    private String loadBalanceStrategy;
+    private String partitioningAttribute;
+    private String loadBalanceCompression;
+
+
+    @ApiModelProperty("The source of the connection.")
+    public ConnectableComponent getSource() {
+        return source;
+    }
+
+    public void setSource(ConnectableComponent source) {
+        this.source = source;
+    }
+
+    @ApiModelProperty("The destination of the connection.")
+    public ConnectableComponent getDestination() {
+        return destination;
+    }
+
+    public void setDestination(ConnectableComponent destination) {
+        this.destination = destination;
+    }
+
+    @ApiModelProperty("The bend points on the connection.")
+    public List<Position> getBends() {
+        return bends;
+    }
+
+    public void setBends(List<Position> bends) {
+        this.bends = bends;
+    }
+
+    @ApiModelProperty("The index of the bend point where to place the connection label.")
+    public Integer getLabelIndex() {
+        return labelIndex;
+    }
+
+    public void setLabelIndex(Integer labelIndex) {
+        this.labelIndex = labelIndex;
+    }
+
+    @ApiModelProperty(
+            value = "The z index of the connection.",
+            name = "zIndex")  // Jackson maps this method name to JSON key "zIndex", but Swagger does not by default
+    public Long getzIndex() {
+        return zIndex;
+    }
+
+    public void setzIndex(Long zIndex) {
+        this.zIndex = zIndex;
+    }
+
+    @ApiModelProperty("The selected relationship that comprise the connection.")
+    public Set<String> getSelectedRelationships() {
+        return selectedRelationships;
+    }
+
+    public void setSelectedRelationships(Set<String> relationships) {
+        this.selectedRelationships = relationships;
+    }
+
+
+    @ApiModelProperty("The object count threshold for determining when back pressure is applied. Updating this value is a passive change in the sense that it won't impact whether existing files "
+        + "over the limit are affected but it does help feeder processors to stop pushing too much into this work queue.")
+    public Long getBackPressureObjectThreshold() {
+        return backPressureObjectThreshold;
+    }
+
+    public void setBackPressureObjectThreshold(Long backPressureObjectThreshold) {
+        this.backPressureObjectThreshold = backPressureObjectThreshold;
+    }
+
+
+    @ApiModelProperty("The object data size threshold for determining when back pressure is applied. Updating this value is a passive change in the sense that it won't impact whether existing "
+        + "files over the limit are affected but it does help feeder processors to stop pushing too much into this work queue.")
+    public String getBackPressureDataSizeThreshold() {
+        return backPressureDataSizeThreshold;
+    }
+
+    public void setBackPressureDataSizeThreshold(String backPressureDataSizeThreshold) {
+        this.backPressureDataSizeThreshold = backPressureDataSizeThreshold;
+    }
+
+
+    @ApiModelProperty("The amount of time a flow file may be in the flow before it will be automatically aged out of the flow. Once a flow file reaches this age it will be terminated from "
+        + "the flow the next time a processor attempts to start work on it.")
+    public String getFlowFileExpiration() {
+        return flowFileExpiration;
+    }
+
+    public void setFlowFileExpiration(String flowFileExpiration) {
+        this.flowFileExpiration = flowFileExpiration;
+    }
+
+
+    @ApiModelProperty("The comparators used to prioritize the queue.")
+    public List<String> getPrioritizers() {
+        return prioritizers;
+    }
+
+    public void setPrioritizers(List<String> prioritizers) {
+        this.prioritizers = prioritizers;
+    }
+
+    @ApiModelProperty(value = "The Strategy to use for load balancing data across the cluster, or null, if no Load Balance Strategy has been specified.",
+            allowableValues = "DO_NOT_LOAD_BALANCE, PARTITION_BY_ATTRIBUTE, ROUND_ROBIN, SINGLE_NODE")
+    public String getLoadBalanceStrategy() {
+        return loadBalanceStrategy;
+    }
+
+    public void setLoadBalanceStrategy(String loadBalanceStrategy) {
+        this.loadBalanceStrategy = loadBalanceStrategy;
+    }
+
+    @ApiModelProperty("The attribute to use for partitioning data as it is load balanced across the cluster. If the Load Balance Strategy is configured to use PARTITION_BY_ATTRIBUTE, the value " +
+            "returned by this method is the name of the FlowFile Attribute that will be used to determine which node in the cluster should receive a given FlowFile. If the Load Balance Strategy is " +
+            "unset or is set to any other value, the Partitioning Attribute has no effect.")
+    public String getPartitioningAttribute() {
+        return partitioningAttribute;
+    }
+
+    public void setPartitioningAttribute(final String partitioningAttribute) {
+        this.partitioningAttribute = partitioningAttribute;
+    }
+
+    @ApiModelProperty(value = "Whether or not compression should be used when transferring FlowFiles between nodes",
+            allowableValues = "DO_NOT_COMPRESS, COMPRESS_ATTRIBUTES_ONLY, COMPRESS_ATTRIBUTES_AND_CONTENT")
+    public String getLoadBalanceCompression() {
+        return loadBalanceCompression;
+    }
+
+    public void setLoadBalanceCompression(final String compression) {
+        this.loadBalanceCompression = compression;
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.CONNECTION;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedControllerService.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedControllerService.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedControllerService.java
new file mode 100644
index 0000000..7b14ac2
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedControllerService.java
@@ -0,0 +1,103 @@
+/*
+ * 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.flow;
+
+import java.util.List;
+import java.util.Map;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedControllerService extends VersionedComponent
+        implements VersionedConfigurableComponent, VersionedExtensionComponent {
+
+    private String type;
+    private Bundle bundle;
+    private List<ControllerServiceAPI> controllerServiceApis;
+
+    private Map<String, String> properties;
+    private Map<String, VersionedPropertyDescriptor> propertyDescriptors;
+    private String annotationData;
+
+
+    @Override
+    @ApiModelProperty(value = "The type of the controller service.")
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    @Override
+    @ApiModelProperty(value = "The details of the artifact that bundled this processor type.")
+    public Bundle getBundle() {
+        return bundle;
+    }
+
+    @Override
+    public void setBundle(Bundle bundle) {
+        this.bundle = bundle;
+    }
+
+    @ApiModelProperty(value = "Lists the APIs this Controller Service implements.")
+    public List<ControllerServiceAPI> getControllerServiceApis() {
+        return controllerServiceApis;
+    }
+
+    public void setControllerServiceApis(List<ControllerServiceAPI> controllerServiceApis) {
+        this.controllerServiceApis = controllerServiceApis;
+    }
+
+    @Override
+    @ApiModelProperty(value = "The properties of the controller service.")
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public void setProperties(Map<String, String> properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    @ApiModelProperty("The property descriptors for the processor.")
+    public Map<String, VersionedPropertyDescriptor> getPropertyDescriptors() {
+        return propertyDescriptors;
+    }
+
+    @Override
+    public void setPropertyDescriptors(Map<String, VersionedPropertyDescriptor> propertyDescriptors) {
+        this.propertyDescriptors = propertyDescriptors;
+    }
+
+    @ApiModelProperty(value = "The annotation for the controller service. This is how the custom UI relays configuration to the controller service.")
+    public String getAnnotationData() {
+        return annotationData;
+    }
+
+    public void setAnnotationData(String annotationData) {
+        this.annotationData = annotationData;
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.CONTROLLER_SERVICE;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedExtensionComponent.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedExtensionComponent.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedExtensionComponent.java
new file mode 100644
index 0000000..e1d514c
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedExtensionComponent.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.flow;
+
+/**
+ * A component that is an extension and has a type and bundle.
+ */
+public interface VersionedExtensionComponent {
+
+    Bundle getBundle();
+
+    void setBundle(Bundle bundle);
+
+    String getType();
+
+    void setType(String type);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlow.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlow.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlow.java
new file mode 100644
index 0000000..6ece46a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlow.java
@@ -0,0 +1,56 @@
+/*
+ * 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.flow;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.registry.bucket.BucketItem;
+import org.apache.nifi.registry.bucket.BucketItemType;
+
+import javax.validation.constraints.Min;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * <p>
+ * Represents a versioned flow. A versioned flow is a named flow that is expected to change
+ * over time. This flow is saved to the registry with information such as its name, a description,
+ * and each version of the flow.
+ * </p>
+ *
+ * @see VersionedFlowSnapshot
+ */
+@XmlRootElement
+@ApiModel(value = "versionedFlow")
+public class VersionedFlow extends BucketItem {
+
+    @Min(0)
+    private long versionCount;
+
+    public VersionedFlow() {
+        super(BucketItemType.Flow);
+    }
+
+    @ApiModelProperty(value = "The number of versions of this flow.", readOnly = true)
+    public long getVersionCount() {
+        return versionCount;
+    }
+
+    public void setVersionCount(long versionCount) {
+        this.versionCount = versionCount;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowCoordinates.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowCoordinates.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowCoordinates.java
new file mode 100644
index 0000000..8e39c5b
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowCoordinates.java
@@ -0,0 +1,101 @@
+/*
+ * 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.flow;
+
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedFlowCoordinates {
+    private String registryUrl;
+    private String bucketId;
+    private String flowId;
+    private int version;
+    private Boolean latest;
+
+    @ApiModelProperty("The URL of the Flow Registry that contains the flow")
+    public String getRegistryUrl() {
+        return registryUrl;
+    }
+
+    public void setRegistryUrl(String registryUrl) {
+        this.registryUrl = registryUrl;
+    }
+
+    @ApiModelProperty("The UUID of the bucket that the flow resides in")
+    public String getBucketId() {
+        return bucketId;
+    }
+
+    public void setBucketId(String bucketId) {
+        this.bucketId = bucketId;
+    }
+
+    @ApiModelProperty("The UUID of the flow")
+    public String getFlowId() {
+        return flowId;
+    }
+
+    public void setFlowId(String flowId) {
+        this.flowId = flowId;
+    }
+
+    @ApiModelProperty("The version of the flow")
+    public int getVersion() {
+        return version;
+    }
+
+    public void setVersion(int version) {
+        this.version = version;
+    }
+
+    @ApiModelProperty("Whether or not these coordinates point to the latest version of the flow")
+    public Boolean getLatest() {
+        return latest;
+    }
+
+    public void setLatest(Boolean latest) {
+        this.latest = latest;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(registryUrl, bucketId, flowId, version);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof VersionedFlowCoordinates)) {
+            return false;
+        }
+
+        final VersionedFlowCoordinates other = (VersionedFlowCoordinates) obj;
+        return Objects.equals(registryUrl, other.registryUrl) && Objects.equals(bucketId, other.bucketId) && Objects.equals(flowId, other.flowId) && Objects.equals(version, other.version);
+    }
+
+    @Override
+    public String toString() {
+        return "VersionedFlowCoordinates[bucketId=" + bucketId + ", flowId=" + flowId + ", version=" + version + ", registryUrl=" + registryUrl + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshot.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshot.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshot.java
new file mode 100644
index 0000000..bc82397
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshot.java
@@ -0,0 +1,128 @@
+/*
+ * 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.flow;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.registry.bucket.Bucket;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import java.util.Objects;
+
+/**
+ * <p>
+ * Represents a snapshot of a versioned flow. A versioned flow may change many times
+ * over the course of its life. Each of these versions that is saved to the registry
+ * is saved as a snapshot, representing information such as the name of the flow, the
+ * version of the flow, the timestamp when it was saved, the contents of the flow, etc.
+ * </p>
+ */
+@ApiModel(value = "versionedFlowSnapshot")
+@XmlRootElement
+public class VersionedFlowSnapshot {
+
+    @Valid
+    @NotNull
+    private VersionedFlowSnapshotMetadata snapshotMetadata;
+
+    @Valid
+    @NotNull
+    private VersionedProcessGroup flowContents;
+
+    // read-only, only populated from retrieval of a snapshot
+    private VersionedFlow flow;
+
+    // read-only, only populated from retrieval of a snapshot
+    private Bucket bucket;
+
+
+    @ApiModelProperty(value = "The metadata for this snapshot", required = true)
+    public VersionedFlowSnapshotMetadata getSnapshotMetadata() {
+        return snapshotMetadata;
+    }
+
+    public void setSnapshotMetadata(VersionedFlowSnapshotMetadata snapshotMetadata) {
+        this.snapshotMetadata = snapshotMetadata;
+    }
+
+    @ApiModelProperty(value = "The contents of the versioned flow", required = true)
+    public VersionedProcessGroup getFlowContents() {
+        return flowContents;
+    }
+
+    public void setFlowContents(VersionedProcessGroup flowContents) {
+        this.flowContents = flowContents;
+    }
+
+    @ApiModelProperty(value = "The flow this snapshot is for", readOnly = true)
+    public VersionedFlow getFlow() {
+        return flow;
+    }
+
+    public void setFlow(VersionedFlow flow) {
+        this.flow = flow;
+    }
+
+    @ApiModelProperty(value = "The bucket where the flow is located", readOnly = true)
+    public Bucket getBucket() {
+        return bucket;
+    }
+
+    public void setBucket(Bucket bucket) {
+        this.bucket = bucket;
+    }
+
+    /**
+     * This is a convenience method that will return true when flow is populated and when the flow's versionCount
+     * is equal to the version of this snapshot.
+     *
+     * @return true if flow is populated and if this snapshot is the latest version for the flow at the time of retrieval
+     */
+    @XmlTransient
+    public boolean isLatest() {
+        return flow != null && snapshotMetadata != null && flow.getVersionCount() == getSnapshotMetadata().getVersion();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.snapshotMetadata);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        final VersionedFlowSnapshot other = (VersionedFlowSnapshot) obj;
+        return Objects.equals(this.snapshotMetadata, other.snapshotMetadata);
+    }
+
+    @Override
+    public String toString() {
+        final String flowName = (flow == null ? "null" : flow.getName());
+        return "VersionedFlowSnapshot[flowId=" + snapshotMetadata.getFlowIdentifier() + ", flowName=" + flowName
+            + ", version=" + snapshotMetadata.getVersion() + ", comments=" + snapshotMetadata.getComments() + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshotMetadata.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshotMetadata.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshotMetadata.java
new file mode 100644
index 0000000..2007279
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFlowSnapshotMetadata.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.flow;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.registry.link.LinkableEntity;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import java.util.Objects;
+
+/**
+ * The metadata information about a VersionedFlowSnapshot. This class implements Comparable in order
+ * to sort based on the snapshot version in ascending order.
+ */
+@ApiModel(value = "versionedFlowSnapshotMetadata")
+public class VersionedFlowSnapshotMetadata extends LinkableEntity implements Comparable<VersionedFlowSnapshotMetadata> {
+
+    @NotBlank
+    private String bucketIdentifier;
+
+    @NotBlank
+    private String flowIdentifier;
+
+    @Min(1)
+    private int version;
+
+    @Min(1)
+    private long timestamp;
+
+    @NotBlank
+    private String author;
+
+    private String comments;
+
+
+    @ApiModelProperty(value = "The identifier of the bucket this snapshot belongs to.", required = true)
+    public String getBucketIdentifier() {
+        return bucketIdentifier;
+    }
+
+    public void setBucketIdentifier(String bucketIdentifier) {
+        this.bucketIdentifier = bucketIdentifier;
+    }
+
+    @ApiModelProperty(value = "The identifier of the flow this snapshot belongs to.", required = true)
+    public String getFlowIdentifier() {
+        return flowIdentifier;
+    }
+
+    public void setFlowIdentifier(String flowIdentifier) {
+        this.flowIdentifier = flowIdentifier;
+    }
+
+    @ApiModelProperty(value = "The version of this snapshot of the flow.", required = true)
+    public int getVersion() {
+        return version;
+    }
+
+    public void setVersion(int version) {
+        this.version = version;
+    }
+
+    @ApiModelProperty(value = "The timestamp when the flow was saved, as milliseconds since epoch.", readOnly = true)
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @ApiModelProperty(value = "The user that created this snapshot of the flow.", readOnly = true)
+    public String getAuthor() {
+        return author;
+    }
+
+    public void setAuthor(String author) {
+        this.author = author;
+    }
+
+    @ApiModelProperty("The comments provided by the user when creating the snapshot.")
+    public String getComments() {
+        return comments;
+    }
+
+    public void setComments(String comments) {
+        this.comments = comments;
+    }
+
+    @Override
+    public int compareTo(final VersionedFlowSnapshotMetadata o) {
+        return o == null ? -1 : Integer.compare(version, o.version);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.flowIdentifier, Integer.valueOf(this.version));
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        final VersionedFlowSnapshotMetadata other = (VersionedFlowSnapshotMetadata) obj;
+
+        return Objects.equals(this.flowIdentifier, other.flowIdentifier)
+                && Objects.equals(this.version, other.version);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFunnel.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFunnel.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFunnel.java
new file mode 100644
index 0000000..871dafc
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedFunnel.java
@@ -0,0 +1,25 @@
+/*
+ * 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.flow;
+
+public class VersionedFunnel extends VersionedComponent {
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.FUNNEL;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedLabel.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedLabel.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedLabel.java
new file mode 100644
index 0000000..f2f7887
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedLabel.java
@@ -0,0 +1,73 @@
+/*
+ * 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.flow;
+
+import java.util.Map;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedLabel extends VersionedComponent {
+    private String label;
+
+    private Double width;
+    private Double height;
+
+    private Map<String, String> style;
+
+
+    @ApiModelProperty("The text that appears in the label.")
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(final String label) {
+        this.label = label;
+    }
+
+    @ApiModelProperty("The styles for this label (font-size : 12px, background-color : #eee, etc).")
+    public Map<String, String> getStyle() {
+        return style;
+    }
+
+    public void setStyle(final Map<String, String> style) {
+        this.style = style;
+    }
+
+    @ApiModelProperty("The height of the label in pixels when at a 1:1 scale.")
+    public Double getHeight() {
+        return height;
+    }
+
+    public void setHeight(Double height) {
+        this.height = height;
+    }
+
+    @ApiModelProperty("The width of the label in pixels when at a 1:1 scale.")
+    public Double getWidth() {
+        return width;
+    }
+
+    public void setWidth(Double width) {
+        this.width = width;
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.LABEL;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPort.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPort.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPort.java
new file mode 100644
index 0000000..f24e386
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPort.java
@@ -0,0 +1,52 @@
+/*
+ * 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.flow;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedPort extends VersionedComponent {
+    private PortType type;
+    private Integer concurrentlySchedulableTaskCount;
+
+    @ApiModelProperty("The number of tasks that should be concurrently scheduled for the port.")
+    public Integer getConcurrentlySchedulableTaskCount() {
+        return concurrentlySchedulableTaskCount;
+    }
+
+    public void setConcurrentlySchedulableTaskCount(Integer concurrentlySchedulableTaskCount) {
+        this.concurrentlySchedulableTaskCount = concurrentlySchedulableTaskCount;
+    }
+
+    @ApiModelProperty("The type of port.")
+    public PortType getType() {
+        return type;
+    }
+
+    public void setType(PortType type) {
+        this.type = type;
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        if (type == PortType.OUTPUT_PORT) {
+            return ComponentType.OUTPUT_PORT;
+        }
+
+        return ComponentType.INPUT_PORT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessGroup.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessGroup.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessGroup.java
new file mode 100644
index 0000000..2acd0a4
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessGroup.java
@@ -0,0 +1,148 @@
+/*
+ * 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.flow;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import io.swagger.annotations.ApiModelProperty;
+
+@XmlRootElement
+public class VersionedProcessGroup extends VersionedComponent {
+
+    private Set<VersionedProcessGroup> processGroups = new HashSet<>();
+    private Set<VersionedRemoteProcessGroup> remoteProcessGroups = new HashSet<>();
+    private Set<VersionedProcessor> processors = new HashSet<>();
+    private Set<VersionedPort> inputPorts = new HashSet<>();
+    private Set<VersionedPort> outputPorts = new HashSet<>();
+    private Set<VersionedConnection> connections = new HashSet<>();
+    private Set<VersionedLabel> labels = new HashSet<>();
+    private Set<VersionedFunnel> funnels = new HashSet<>();
+    private Set<VersionedControllerService> controllerServices = new HashSet<>();
+    private VersionedFlowCoordinates versionedFlowCoordinates = null;
+
+    private Map<String, String> variables = new HashMap<>();
+
+    @ApiModelProperty("The child Process Groups")
+    public Set<VersionedProcessGroup> getProcessGroups() {
+        return processGroups;
+    }
+
+    public void setProcessGroups(Set<VersionedProcessGroup> processGroups) {
+        this.processGroups = new HashSet<>(processGroups);
+    }
+
+    @ApiModelProperty("The Remote Process Groups")
+    public Set<VersionedRemoteProcessGroup> getRemoteProcessGroups() {
+        return remoteProcessGroups;
+    }
+
+    public void setRemoteProcessGroups(Set<VersionedRemoteProcessGroup> remoteProcessGroups) {
+        this.remoteProcessGroups = new HashSet<>(remoteProcessGroups);
+    }
+
+    @ApiModelProperty("The Processors")
+    public Set<VersionedProcessor> getProcessors() {
+        return processors;
+    }
+
+    public void setProcessors(Set<VersionedProcessor> processors) {
+        this.processors = new HashSet<>(processors);
+    }
+
+    @ApiModelProperty("The Input Ports")
+    public Set<VersionedPort> getInputPorts() {
+        return inputPorts;
+    }
+
+    public void setInputPorts(Set<VersionedPort> inputPorts) {
+        this.inputPorts = new HashSet<>(inputPorts);
+    }
+
+    @ApiModelProperty("The Output Ports")
+    public Set<VersionedPort> getOutputPorts() {
+        return outputPorts;
+    }
+
+    public void setOutputPorts(Set<VersionedPort> outputPorts) {
+        this.outputPorts = new HashSet<>(outputPorts);
+    }
+
+    @ApiModelProperty("The Connections")
+    public Set<VersionedConnection> getConnections() {
+        return connections;
+    }
+
+    public void setConnections(Set<VersionedConnection> connections) {
+        this.connections = new HashSet<>(connections);
+    }
+
+    @ApiModelProperty("The Labels")
+    public Set<VersionedLabel> getLabels() {
+        return labels;
+    }
+
+    public void setLabels(Set<VersionedLabel> labels) {
+        this.labels = new HashSet<>(labels);
+    }
+
+    @ApiModelProperty("The Funnels")
+    public Set<VersionedFunnel> getFunnels() {
+        return funnels;
+    }
+
+    public void setFunnels(Set<VersionedFunnel> funnels) {
+        this.funnels = new HashSet<>(funnels);
+    }
+
+    @ApiModelProperty("The Controller Services")
+    public Set<VersionedControllerService> getControllerServices() {
+        return controllerServices;
+    }
+
+    public void setControllerServices(Set<VersionedControllerService> controllerServices) {
+        this.controllerServices = new HashSet<>(controllerServices);
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.PROCESS_GROUP;
+    }
+
+    public void setVariables(Map<String, String> variables) {
+        this.variables = variables;
+    }
+
+    @ApiModelProperty("The Variables in the Variable Registry for this Process Group (not including any ancestor or descendant Process Groups)")
+    public Map<String, String> getVariables() {
+        return variables;
+    }
+
+    public void setVersionedFlowCoordinates(VersionedFlowCoordinates flowCoordinates) {
+        this.versionedFlowCoordinates = flowCoordinates;
+    }
+
+    @ApiModelProperty("The coordinates where the remote flow is stored, or null if the Process Group is not directly under Version Control")
+    public VersionedFlowCoordinates getVersionedFlowCoordinates() {
+        return versionedFlowCoordinates;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessor.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessor.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessor.java
new file mode 100644
index 0000000..aef6dcc
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedProcessor.java
@@ -0,0 +1,197 @@
+/*
+ * 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.flow;
+
+import java.util.Map;
+import java.util.Set;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedProcessor extends VersionedComponent
+        implements VersionedConfigurableComponent, VersionedExtensionComponent {
+
+    private Bundle bundle;
+    private Map<String, String> style;
+
+    private String type;
+    private Map<String, String> properties;
+    private Map<String, VersionedPropertyDescriptor> propertyDescriptors;
+    private String annotationData;
+
+    private String schedulingPeriod;
+    private String schedulingStrategy;
+    private String executionNode;
+    private String penaltyDuration;
+    private String yieldDuration;
+    private String bulletinLevel;
+    private Long runDurationMillis;
+    private Integer concurrentlySchedulableTaskCount;
+    private Set<String> autoTerminatedRelationships;
+
+
+    @ApiModelProperty("The frequency with which to schedule the processor. The format of the value will depend on th value of schedulingStrategy.")
+    public String getSchedulingPeriod() {
+        return schedulingPeriod;
+    }
+
+    public void setSchedulingPeriod(String setSchedulingPeriod) {
+        this.schedulingPeriod = setSchedulingPeriod;
+    }
+
+    @ApiModelProperty("Indcates whether the prcessor should be scheduled to run in event or timer driven mode.")
+    public String getSchedulingStrategy() {
+        return schedulingStrategy;
+    }
+
+    public void setSchedulingStrategy(String schedulingStrategy) {
+        this.schedulingStrategy = schedulingStrategy;
+    }
+
+    @Override
+    @ApiModelProperty("The type of Processor")
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public void setType(final String type) {
+        this.type = type;
+    }
+
+    @ApiModelProperty("Indicates the node where the process will execute.")
+    public String getExecutionNode() {
+        return executionNode;
+    }
+
+    public void setExecutionNode(String executionNode) {
+        this.executionNode = executionNode;
+    }
+
+    @ApiModelProperty("The amout of time that is used when the process penalizes a flowfile.")
+    public String getPenaltyDuration() {
+        return penaltyDuration;
+    }
+
+    public void setPenaltyDuration(String penaltyDuration) {
+        this.penaltyDuration = penaltyDuration;
+    }
+
+    @ApiModelProperty("The amount of time that must elapse before this processor is scheduled again after yielding.")
+    public String getYieldDuration() {
+        return yieldDuration;
+    }
+
+    public void setYieldDuration(String yieldDuration) {
+        this.yieldDuration = yieldDuration;
+    }
+
+    @ApiModelProperty("The level at which the processor will report bulletins.")
+    public String getBulletinLevel() {
+        return bulletinLevel;
+    }
+
+    public void setBulletinLevel(String bulletinLevel) {
+        this.bulletinLevel = bulletinLevel;
+    }
+
+    @ApiModelProperty("The number of tasks that should be concurrently schedule for the processor. If the processor doesn't allow parallol processing then any positive input will be ignored.")
+    public Integer getConcurrentlySchedulableTaskCount() {
+        return concurrentlySchedulableTaskCount;
+    }
+
+    public void setConcurrentlySchedulableTaskCount(Integer concurrentlySchedulableTaskCount) {
+        this.concurrentlySchedulableTaskCount = concurrentlySchedulableTaskCount;
+    }
+
+    @Override
+    @ApiModelProperty("The properties for the processor. Properties whose value is not set will only contain the property name.")
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public void setProperties(Map<String, String> properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    @ApiModelProperty("The property descriptors for the processor.")
+    public Map<String, VersionedPropertyDescriptor> getPropertyDescriptors() {
+        return propertyDescriptors;
+    }
+
+    @Override
+    public void setPropertyDescriptors(Map<String, VersionedPropertyDescriptor> propertyDescriptors) {
+        this.propertyDescriptors = propertyDescriptors;
+    }
+
+    @ApiModelProperty("The annotation data for the processor used to relay configuration between a custom UI and the procesosr.")
+    public String getAnnotationData() {
+        return annotationData;
+    }
+
+    public void setAnnotationData(String annotationData) {
+        this.annotationData = annotationData;
+    }
+
+
+    @ApiModelProperty("The names of all relationships that cause a flow file to be terminated if the relationship is not connected elsewhere. This property differs "
+        + "from the 'isAutoTerminate' property of the RelationshipDTO in that the RelationshipDTO is meant to depict the current configuration, whereas this "
+        + "property can be set in a DTO when updating a Processor in order to change which Relationships should be auto-terminated.")
+    public Set<String> getAutoTerminatedRelationships() {
+        return autoTerminatedRelationships;
+    }
+
+    public void setAutoTerminatedRelationships(final Set<String> autoTerminatedRelationships) {
+        this.autoTerminatedRelationships = autoTerminatedRelationships;
+    }
+
+    @ApiModelProperty("The run duration for the processor in milliseconds.")
+    public Long getRunDurationMillis() {
+        return runDurationMillis;
+    }
+
+    public void setRunDurationMillis(Long runDurationMillis) {
+        this.runDurationMillis = runDurationMillis;
+    }
+
+    @Override
+    @ApiModelProperty("Information about the bundle from which the component came")
+    public Bundle getBundle() {
+        return bundle;
+    }
+
+    @Override
+    public void setBundle(Bundle bundle) {
+        this.bundle = bundle;
+    }
+
+    @ApiModelProperty("Stylistic data for rendering in a UI")
+    public Map<String, String> getStyle() {
+        return style;
+    }
+
+    public void setStyle(Map<String, String> style) {
+        this.style = style;
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.PROCESSOR;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPropertyDescriptor.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPropertyDescriptor.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPropertyDescriptor.java
new file mode 100644
index 0000000..2fa9463
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedPropertyDescriptor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.flow;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedPropertyDescriptor {
+    private String name;
+    private String displayName;
+    private boolean identifiesControllerService;
+    private boolean sensitive;
+
+    @ApiModelProperty("The name of the property")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @ApiModelProperty("The display name of the property")
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    @ApiModelProperty("Whether or not the property provides the identifier of a Controller Service")
+    public boolean getIdentifiesControllerService() {
+        return identifiesControllerService;
+    }
+
+    public void setIdentifiesControllerService(boolean identifiesControllerService) {
+        this.identifiesControllerService = identifiesControllerService;
+    }
+
+    @ApiModelProperty("Whether or not the property is considered sensitive")
+    public boolean isSensitive() {
+        return sensitive;
+    }
+
+    public void setSensitive(boolean sensitive) {
+        this.sensitive = sensitive;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteGroupPort.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteGroupPort.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteGroupPort.java
new file mode 100644
index 0000000..ca85ce4
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteGroupPort.java
@@ -0,0 +1,109 @@
+/*
+ * 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.flow;
+
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class VersionedRemoteGroupPort extends VersionedComponent {
+    private String remoteGroupId;
+    private Integer concurrentlySchedulableTaskCount;
+    private Boolean useCompression;
+    private BatchSize batchSize;
+    private ComponentType componentType;
+    private String targetId;
+
+    @ApiModelProperty("The number of task that may transmit flowfiles to the target port concurrently.")
+    public Integer getConcurrentlySchedulableTaskCount() {
+        return concurrentlySchedulableTaskCount;
+    }
+
+    public void setConcurrentlySchedulableTaskCount(Integer concurrentlySchedulableTaskCount) {
+        this.concurrentlySchedulableTaskCount = concurrentlySchedulableTaskCount;
+    }
+
+    @ApiModelProperty("The id of the remote process group that the port resides in.")
+    public String getRemoteGroupId() {
+        return remoteGroupId;
+    }
+
+    public void setRemoteGroupId(String groupId) {
+        this.remoteGroupId = groupId;
+    }
+
+
+    @ApiModelProperty("Whether the flowfiles are compressed when sent to the target port.")
+    public Boolean isUseCompression() {
+        return useCompression;
+    }
+
+    public void setUseCompression(Boolean useCompression) {
+        this.useCompression = useCompression;
+    }
+
+    @ApiModelProperty("The batch settings for data transmission.")
+    public BatchSize getBatchSize() {
+        return batchSize;
+    }
+
+    public void setBatchSize(BatchSize batchSize) {
+        this.batchSize = batchSize;
+    }
+
+    @ApiModelProperty("The ID of the port on the target NiFi instance")
+    public String getTargetId() {
+        return targetId;
+    }
+
+    public void setTargetId(final String targetId) {
+        this.targetId = targetId;
+    }
+
+    @Override
+    public int hashCode() {
+        return 923847 + String.valueOf(getName()).hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof VersionedRemoteGroupPort)) {
+            return false;
+        }
+
+        final VersionedRemoteGroupPort other = (VersionedRemoteGroupPort) obj;
+        return Objects.equals(getName(), other.getName());
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return componentType;
+    }
+
+    @Override
+    public void setComponentType(final ComponentType componentType) {
+        if (componentType != ComponentType.REMOTE_INPUT_PORT && componentType != ComponentType.REMOTE_OUTPUT_PORT) {
+            throw new IllegalArgumentException();
+        }
+
+        this.componentType = componentType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteProcessGroup.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteProcessGroup.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteProcessGroup.java
new file mode 100644
index 0000000..834afac
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/flow/VersionedRemoteProcessGroup.java
@@ -0,0 +1,161 @@
+/*
+ * 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.flow;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Set;
+
+public class VersionedRemoteProcessGroup extends VersionedComponent {
+    private String targetUri;
+    private String targetUris;
+
+    private String communicationsTimeout;
+    private String yieldDuration;
+    private String transportProtocol;
+    private String localNetworkInterface;
+    private String proxyHost;
+    private Integer proxyPort;
+    private String proxyUser;
+
+    private Set<VersionedRemoteGroupPort> inputPorts;
+    private Set<VersionedRemoteGroupPort> outputPorts;
+
+
+    @Deprecated
+    @ApiModelProperty(
+            value = "[DEPRECATED] The target URI of the remote process group." +
+                    " If target uri is not set, but uris are set, then returns the first uri in the uris." +
+                    " If neither target uri nor uris are set, then returns null.",
+            notes = "This field is deprecated and will be removed in version 1.x of NiFi Registry." +
+                    " Please migrate to using targetUris only.")
+    public String getTargetUri() {
+
+        if (!StringUtils.isEmpty(targetUri)) {
+            return targetUri;
+        }
+        return !StringUtils.isEmpty(targetUris) ? targetUris.split(",", 2)[0] : null;
+
+    }
+
+    public void setTargetUri(final String targetUri) {
+        this.targetUri = targetUri;
+    }
+
+    @ApiModelProperty(
+            value = "The target URIs of the remote process group." +
+                    " If target uris is not set but target uri is set, then returns the single target uri." +
+                    " If neither target uris nor target uri is set, then returns null.")
+    public String getTargetUris() {
+
+        if (!StringUtils.isEmpty(targetUris)) {
+            return targetUris;
+        }
+        return !StringUtils.isEmpty(targetUri) ? targetUri : null;
+
+    }
+
+    public void setTargetUris(String targetUris) {
+        this.targetUris = targetUris;
+    }
+
+    @ApiModelProperty("The time period used for the timeout when communicating with the target.")
+    public String getCommunicationsTimeout() {
+        return communicationsTimeout;
+    }
+
+    public void setCommunicationsTimeout(String communicationsTimeout) {
+        this.communicationsTimeout = communicationsTimeout;
+    }
+
+    @ApiModelProperty("When yielding, this amount of time must elapse before the remote process group is scheduled again.")
+    public String getYieldDuration() {
+        return yieldDuration;
+    }
+
+    public void setYieldDuration(String yieldDuration) {
+        this.yieldDuration = yieldDuration;
+    }
+
+    @ApiModelProperty(value = "The Transport Protocol that is used for Site-to-Site communications", allowableValues = "RAW, HTTP")
+    public String getTransportProtocol() {
+        return transportProtocol;
+    }
+
+    public void setTransportProtocol(String transportProtocol) {
+        this.transportProtocol = transportProtocol;
+    }
+
+    @ApiModelProperty("A Set of Input Ports that can be connected to, in order to send data to the remote NiFi instance")
+    public Set<VersionedRemoteGroupPort> getInputPorts() {
+        return inputPorts;
+    }
+
+    public void setInputPorts(Set<VersionedRemoteGroupPort> inputPorts) {
+        this.inputPorts = inputPorts;
+    }
+
+    @ApiModelProperty("A Set of Output Ports that can be connected to, in order to pull data from the remote NiFi instance")
+    public Set<VersionedRemoteGroupPort> getOutputPorts() {
+        return outputPorts;
+    }
+
+    public void setOutputPorts(Set<VersionedRemoteGroupPort> outputPorts) {
+        this.outputPorts = outputPorts;
+    }
+
+
+    @ApiModelProperty("The local network interface to send/receive data. If not specified, any local address is used. If clustered, all nodes must have an interface with this identifier.")
+    public String getLocalNetworkInterface() {
+        return localNetworkInterface;
+    }
+
+    public void setLocalNetworkInterface(String localNetworkInterface) {
+        this.localNetworkInterface = localNetworkInterface;
+    }
+
+    public String getProxyHost() {
+        return proxyHost;
+    }
+
+    public void setProxyHost(String proxyHost) {
+        this.proxyHost = proxyHost;
+    }
+
+    public Integer getProxyPort() {
+        return proxyPort;
+    }
+
+    public void setProxyPort(Integer proxyPort) {
+        this.proxyPort = proxyPort;
+    }
+
+    public String getProxyUser() {
+        return proxyUser;
+    }
+
+    public void setProxyUser(String proxyUser) {
+        this.proxyUser = proxyUser;
+    }
+
+    @Override
+    public ComponentType getComponentType() {
+        return ComponentType.REMOTE_PROCESS_GROUP;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkAdapter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkAdapter.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkAdapter.java
new file mode 100644
index 0000000..c3dae90
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.link;
+
+import javax.ws.rs.core.Link;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.namespace.QName;
+import java.util.Map;
+
+/**
+ * This class is a modified version of Jersey's Link.JaxbAdapter that adds protection against nulls.
+ */
+public class LinkAdapter extends XmlAdapter<Link.JaxbLink, Link> {
+
+    /**
+     * Convert a {@link Link.JaxbLink} into a {@link Link}.
+     *
+     * @param v instance of type {@link Link.JaxbLink}.
+     * @return mapped instance of type {@link Link.JaxbLink}
+     */
+    @Override
+    public Link unmarshal(Link.JaxbLink v) {
+        if (v == null) {
+            return null;
+        }
+
+        Link.Builder lb = Link.fromUri(v.getUri());
+        for (Map.Entry<QName, Object> e : v.getParams().entrySet()) {
+            lb.param(e.getKey().getLocalPart(), e.getValue().toString());
+        }
+        return lb.build();
+    }
+
+    /**
+     * Convert a {@link Link} into a {@link Link.JaxbLink}.
+     *
+     * @param v instance of type {@link Link}.
+     * @return mapped instance of type {@link Link.JaxbLink}.
+     */
+    @Override
+    public Link.JaxbLink marshal(Link v) {
+        if (v == null) {
+           return null;
+        }
+
+        Link.JaxbLink jl = new Link.JaxbLink(v.getUri());
+        for (Map.Entry<String, String> e : v.getParams().entrySet()) {
+            final String name = e.getKey();
+            jl.getParams().put(new QName("", name), e.getValue());
+        }
+        return jl;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkableEntity.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkableEntity.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkableEntity.java
new file mode 100644
index 0000000..896e9d3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/link/LinkableEntity.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.link;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.ws.rs.core.Link;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+/**
+ * Base classes for domain objects that want to provide a hypermedia link.
+ */
+@ApiModel("linkableEntity")
+public abstract class LinkableEntity {
+
+    private Link link;
+
+    @XmlElement
+    @XmlJavaTypeAdapter(LinkAdapter.class)
+    @ApiModelProperty(value = "An WebLink to this entity.", readOnly = true)
+    public Link getLink() {
+        return link;
+    }
+
+    public void setLink(Link link) {
+        this.link = link;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortOrder.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortOrder.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortOrder.java
new file mode 100644
index 0000000..8e571de
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortOrder.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.params;
+
+public enum SortOrder {
+
+    ASC("asc"),
+
+    DESC("desc");
+
+    private final String name;
+
+    SortOrder(final String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public static SortOrder fromString(String order) {
+        if (ASC.getName().equals(order)) {
+            return  ASC;
+        }
+
+        if (DESC.getName().equals(order)) {
+            return DESC;
+        }
+
+        throw new IllegalArgumentException("Unknown Sort Order: " + order);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortParameter.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortParameter.java b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortParameter.java
new file mode 100644
index 0000000..d4a1add
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/params/SortParameter.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.params;
+
+/**
+ * Sort parameter made up of a field and a sort order.
+ */
+public class SortParameter {
+
+    public static final String API_PARAM_DESCRIPTION =
+            "Apply client-defined sorting to the resulting list of resource objects. " +
+                    "The value of this parameter should be in the format \"field:order\". " +
+                    "Valid values for 'field' can be discovered via GET :resourceURI/fields. " +
+                    "Valid values for 'order' are 'ASC' (ascending order), 'DESC' (descending order).";
+
+    private final String fieldName;
+
+    private final SortOrder order;
+
+    public SortParameter(final String fieldName, final SortOrder order) {
+        this.fieldName = fieldName;
+        this.order = order;
+
+        if (this.fieldName == null) {
+            throw new IllegalStateException("Field Name cannot be null");
+        }
+
+        if (this.fieldName.trim().isEmpty()) {
+            throw new IllegalStateException("Field Name cannot be blank");
+        }
+
+        if (this.order == null) {
+            throw new IllegalStateException("Order cannot be null");
+        }
+    }
+
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    public SortOrder getOrder() {
+        return order;
+    }
+
+    /**
+     * Parses a sorting expression of the form field:order.
+     *
+     * @param sortExpression the expression
+     * @return the Sort instance
+     */
+    public static SortParameter fromString(final String sortExpression) {
+        if (sortExpression == null) {
+            throw new IllegalArgumentException("Sort cannot be null");
+        }
+
+        final String[] sortParts = sortExpression.split("[:]");
+        if (sortParts.length != 2) {
+            throw new IllegalArgumentException("Sort must be in the form field:order");
+        }
+
+        final String fieldName = sortParts[0];
+        final SortOrder order = SortOrder.fromString(sortParts[1]);
+
+        return new SortParameter(fieldName, order);
+    }
+
+    @Override
+    public String toString() {
+        return fieldName + ":" + order.getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-data-model/src/test/java/org/apache/nifi/registry/flow/TestVersionedRemoteProcessGroup.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-data-model/src/test/java/org/apache/nifi/registry/flow/TestVersionedRemoteProcessGroup.java b/nifi-registry-core/nifi-registry-data-model/src/test/java/org/apache/nifi/registry/flow/TestVersionedRemoteProcessGroup.java
new file mode 100644
index 0000000..bbe6724
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-data-model/src/test/java/org/apache/nifi/registry/flow/TestVersionedRemoteProcessGroup.java
@@ -0,0 +1,102 @@
+/*
+ * 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.flow;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestVersionedRemoteProcessGroup {
+
+    @Test
+    public void testGetTargetUriAndGetTargetUris() {
+
+        VersionedRemoteProcessGroup vRPG = new VersionedRemoteProcessGroup();
+
+
+        /* targetUri is null, targetUris varies */
+
+        vRPG.setTargetUri(null);
+        vRPG.setTargetUris(null);
+        assertEquals(null, vRPG.getTargetUri());
+        assertEquals(null, vRPG.getTargetUris());
+
+        vRPG.setTargetUri(null);
+        vRPG.setTargetUris("");
+        assertEquals(null, vRPG.getTargetUri());
+        assertEquals(null, vRPG.getTargetUris());
+
+        vRPG.setTargetUri(null);
+        vRPG.setTargetUris("uri-2");
+        //assertEquals("uri-2", vRPG.getTargetUri());
+        assertEquals("uri-2", vRPG.getTargetUris());
+
+        vRPG.setTargetUri(null);
+        vRPG.setTargetUris("uri-2,uri-3");
+        assertEquals("uri-2", vRPG.getTargetUri());
+        assertEquals("uri-2,uri-3", vRPG.getTargetUris());
+
+
+        /* targetUri is empty, targetUris varies */
+
+        vRPG.setTargetUri("");
+        vRPG.setTargetUris(null);
+        assertEquals(null, vRPG.getTargetUri());
+        assertEquals(null, vRPG.getTargetUris());
+
+        vRPG.setTargetUri("");
+        vRPG.setTargetUris("");
+        assertEquals(null, vRPG.getTargetUri());
+        assertEquals(null, vRPG.getTargetUris());
+
+        vRPG.setTargetUri("");
+        vRPG.setTargetUris("uri-2");
+        assertEquals("uri-2", vRPG.getTargetUri());
+        assertEquals("uri-2", vRPG.getTargetUris());
+
+        vRPG.setTargetUri("");
+        vRPG.setTargetUris("uri-2,uri-3");
+        assertEquals("uri-2", vRPG.getTargetUri());
+        assertEquals("uri-2,uri-3", vRPG.getTargetUris());
+
+
+        /* targetUri is set, targetUris varies */
+
+        vRPG.setTargetUri("uri-1");
+        vRPG.setTargetUris(null);
+        assertEquals("uri-1", vRPG.getTargetUri());
+        assertEquals("uri-1", vRPG.getTargetUris());
+
+        vRPG.setTargetUri("uri-1");
+        vRPG.setTargetUris("");
+        assertEquals("uri-1", vRPG.getTargetUri());
+        assertEquals("uri-1", vRPG.getTargetUris());
+
+        vRPG.setTargetUri("uri-1");
+        vRPG.setTargetUris("uri-2");
+        assertEquals("uri-1", vRPG.getTargetUri());
+        assertEquals("uri-2", vRPG.getTargetUris());
+
+        vRPG.setTargetUri("uri-1");
+        vRPG.setTargetUris("uri-2,uri-3");
+        assertEquals("uri-1", vRPG.getTargetUri());
+        assertEquals("uri-2,uri-3", vRPG.getTargetUris());
+
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-docker/dockerhub/.dockerignore
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-docker/dockerhub/.dockerignore b/nifi-registry-core/nifi-registry-docker/dockerhub/.dockerignore
new file mode 100644
index 0000000..30a2650
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-docker/dockerhub/.dockerignore
@@ -0,0 +1,19 @@
+# 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.
+
+# Place files you want to exclude from the docker build here similar to .gitignore https://docs.docker.com/engine/reference/builder/#dockerignore-file
+DockerBuild.sh
+DockerRun.sh
+DockerImage.txt
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-docker/dockerhub/DockerBuild.sh
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-docker/dockerhub/DockerBuild.sh b/nifi-registry-core/nifi-registry-docker/dockerhub/DockerBuild.sh
new file mode 100755
index 0000000..c7e01e3
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-docker/dockerhub/DockerBuild.sh
@@ -0,0 +1,36 @@
+# 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.
+
+#!/bin/bash
+
+DOCKER_UID=1000
+if [ -n "$1" ]; then
+  DOCKER_UID="$1"
+fi
+
+DOCKER_GID=1000
+if [ -n "$2" ]; then
+  DOCKER_GID="$2"
+fi
+
+MIRROR=https://archive.apache.org/dist
+if [ -n "$3" ]; then
+  MIRROR="$3"
+fi
+
+DOCKER_IMAGE="$(egrep -v '(^#|^\s*$|^\s*\t*#)' DockerImage.txt)"
+NIFI_REGISTRY_IMAGE_VERSION="$(echo $DOCKER_IMAGE | cut -d : -f 2)"
+echo "Building NiFi-Registry Image: '$DOCKER_IMAGE' Version: NIFI_REGISTRY_IMAGE_VERSION Mirror: $MIRROR"
+docker build --build-arg UID="$DOCKER_UID" --build-arg GID="$DOCKER_GID" --build-arg NIFI_REGISTRY_VERSION="$NIFI_REGISTRY_IMAGE_VERSION" --build-arg MIRROR="$MIRROR" -t $DOCKER_IMAGE .

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-docker/dockerhub/DockerImage.txt
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-docker/dockerhub/DockerImage.txt b/nifi-registry-core/nifi-registry-docker/dockerhub/DockerImage.txt
new file mode 100644
index 0000000..07d6e8d
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-docker/dockerhub/DockerImage.txt
@@ -0,0 +1,16 @@
+# 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.
+
+apache/nifi-registry:0.3.0

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-docker/dockerhub/Dockerfile
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-docker/dockerhub/Dockerfile b/nifi-registry-core/nifi-registry-docker/dockerhub/Dockerfile
new file mode 100644
index 0000000..5d78ca0
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-docker/dockerhub/Dockerfile
@@ -0,0 +1,56 @@
+# 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.
+#
+
+FROM openjdk:8-jdk-slim
+LABEL maintainer="Apache NiFi <de...@nifi.apache.org>"
+LABEL site="https://nifi.apache.org"
+
+ARG UID=1000
+ARG GID=1000
+ARG NIFI_REGISTRY_VERSION=0.3.0
+ARG MIRROR=https://archive.apache.org/dist
+
+ENV NIFI_REGISTRY_BASE_DIR /opt/nifi-registry
+ENV NIFI_REGISTRY_HOME=${NIFI_REGISTRY_BASE_DIR}/nifi-registry-${NIFI_REGISTRY_VERSION} \
+    NIFI_REGISTRY_BINARY_URL=nifi/nifi-registry/nifi-registry-${NIFI_REGISTRY_VERSION}/nifi-registry-${NIFI_REGISTRY_VERSION}-bin.tar.gz
+
+ADD sh/ ${NIFI_REGISTRY_BASE_DIR}/scripts/
+
+# Setup NiFi-Registry user
+RUN groupadd -g ${GID} nifi || groupmod -n nifi `getent group ${GID} | cut -d: -f1` \
+    && useradd --shell /bin/bash -u ${UID} -g ${GID} -m nifi \
+    && chown -R nifi:nifi ${NIFI_REGISTRY_BASE_DIR} \
+    && apt-get update -y \
+    && apt-get install -y curl jq xmlstarlet
+
+USER nifi
+
+# Download, validate, and expand Apache NiFi-Registry binary.
+RUN curl -fSL ${MIRROR}/${NIFI_REGISTRY_BINARY_URL} -o ${NIFI_REGISTRY_BASE_DIR}/nifi-registry-${NIFI_REGISTRY_VERSION}-bin.tar.gz \
+    && echo "$(curl ${MIRROR}/${NIFI_REGISTRY_BINARY_URL}.sha256) *${NIFI_REGISTRY_BASE_DIR}/nifi-registry-${NIFI_REGISTRY_VERSION}-bin.tar.gz" | sha256sum -c - \
+    && tar -xvzf ${NIFI_REGISTRY_BASE_DIR}/nifi-registry-${NIFI_REGISTRY_VERSION}-bin.tar.gz -C ${NIFI_REGISTRY_BASE_DIR} \
+    && rm ${NIFI_REGISTRY_BASE_DIR}/nifi-registry-${NIFI_REGISTRY_VERSION}-bin.tar.gz \
+    && chown -R nifi:nifi ${NIFI_REGISTRY_HOME}
+
+# Web HTTP(s) ports
+EXPOSE 18080 18443
+
+WORKDIR ${NIFI_REGISTRY_HOME}
+
+# Apply configuration and start NiFi Registry
+CMD ${NIFI_REGISTRY_BASE_DIR}/scripts/start.sh