You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jo...@apache.org on 2016/07/14 22:33:08 UTC

[02/10] nifi git commit: NIFI-1896 This closes #650. Refactored nifi-api into nifi-framework-api and other locations. The nifi-api is specific to that which is needed for intended extension points.

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDescriptor.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDescriptor.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDescriptor.java
new file mode 100644
index 0000000..9412ee1
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDescriptor.java
@@ -0,0 +1,93 @@
+/*
+ * 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.web;
+
+import java.util.Map;
+
+public class ComponentDescriptor {
+
+    private final String name;
+    private final String displayName;
+    private final String description;
+    private final String defaultValue;
+    private final Map<String,String> allowableValues;
+
+    private ComponentDescriptor(Builder builder){
+        this.name = builder.name;
+        this.displayName = builder.displayName;
+        this.description = builder.description;
+        this.defaultValue = builder.defaultValue;
+        this.allowableValues = builder.allowableValues;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public Map<String,String> getAllowableValues() {
+        return allowableValues;
+    }
+
+    public static final class Builder{
+        private String name;
+        private String displayName;
+        private String description;
+        private String defaultValue;
+        private Map<String,String> allowableValues;
+
+        public Builder name(String name){
+            this.name = name;
+            return this;
+        }
+
+        public Builder displayName(String displayName){
+            this.displayName = displayName;
+            return this;
+        }
+
+        public  Builder description(String description){
+            this.description = description;
+            return this;
+        }
+
+        public Builder defaultValue(String defaultValue){
+            this.defaultValue = defaultValue;
+            return this;
+        }
+
+        public Builder allowableValues(Map<String,String> allowableValues){
+            this.allowableValues = allowableValues;
+            return this;
+        }
+
+        public ComponentDescriptor build(){
+            return new ComponentDescriptor(this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDetails.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDetails.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDetails.java
new file mode 100644
index 0000000..9d163e4
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/ComponentDetails.java
@@ -0,0 +1,163 @@
+/*
+ * 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.web;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Details about a given component. Contains configuration and current
+ * validation errors.
+ */
+public class ComponentDetails {
+
+    private final String id;
+    private final String name;
+    private final String type;
+    private final String state;
+    private final String annotationData;
+    private final Map<String, String> properties;
+    private final Map<String, ComponentDescriptor> descriptors;
+
+    private final Collection<String> validationErrors;
+
+    private ComponentDetails(final Builder builder) {
+        this.id = builder.id;
+        this.name = builder.name;
+        this.type = builder.type;
+        this.state = builder.state;
+        this.annotationData = builder.annotationData;
+        this.properties = builder.properties;
+        this.descriptors = builder.descriptors;
+        this.validationErrors = builder.validationErrors;
+
+    }
+
+    /**
+     * @return component id
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * @return component name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return component type
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * @return component state
+     */
+    public String getState() {
+        return state;
+    }
+
+    /**
+     * @return component's annotation data
+     */
+    public String getAnnotationData() {
+        return annotationData;
+    }
+
+    /**
+     * @return Mapping of component properties
+     */
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+
+    /**
+     * @return Mapping of component descriptors
+     */
+    public Map<String,ComponentDescriptor> getDescriptors(){
+        return descriptors;
+    }
+
+    /**
+     * @return Current validation errors for the component
+     */
+    public Collection<String> getValidationErrors() {
+        return validationErrors;
+    }
+
+    public static final class Builder {
+
+        private String id;
+        private String name;
+        private String type;
+        private String state;
+        private String annotationData;
+        private Map<String, String> properties;
+        private Map<String,ComponentDescriptor> descriptors;
+
+        private Collection<String> validationErrors;
+
+        public Builder id(final String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder name(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder type(final String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder state(final String state) {
+            this.state = state;
+            return this;
+        }
+
+        public Builder annotationData(final String annotationData) {
+            this.annotationData = annotationData;
+            return this;
+        }
+
+        public Builder properties(final Map<String, String> properties) {
+            this.properties = properties;
+            return this;
+        }
+
+        public Builder validateErrors(final Collection<String> validationErrors) {
+            this.validationErrors = validationErrors;
+            return this;
+        }
+
+        public Builder descriptors(final Map<String,ComponentDescriptor> descriptors){
+            this.descriptors = descriptors;
+            return this;
+        }
+
+        public ComponentDetails build() {
+            return new ComponentDetails(this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/ConfigurationAction.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/ConfigurationAction.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/ConfigurationAction.java
new file mode 100644
index 0000000..96f2abf
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/ConfigurationAction.java
@@ -0,0 +1,125 @@
+/*
+ * 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.web;
+
+/**
+ * An action that represents the configuration of a component.
+ */
+public class ConfigurationAction {
+
+    private final String id;
+    private final String name;
+    private final String type;
+    private final String field;
+    private final String previousValue;
+    private final String value;
+
+    private ConfigurationAction(final Builder builder) {
+        this.id = builder.id;
+        this.name = builder.name;
+        this.type = builder.type;
+        this.field = builder.field;
+        this.previousValue = builder.previousValue;
+        this.value = builder.value;
+    }
+
+    /**
+     * @return id of the component being modified
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * @return name of the component being modified
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return type of the component being modified
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * @return the name of the field, property, etc that has been modified
+     */
+    public String getField() {
+        return field;
+    }
+
+    /**
+     * @return the previous value
+     */
+    public String getPreviousValue() {
+        return previousValue;
+    }
+
+    /**
+     * @return the new value
+     */
+    public String getValue() {
+        return value;
+    }
+
+    public static class Builder {
+
+        private String id;
+        private String name;
+        private String type;
+        private String field;
+        private String previousValue;
+        private String value;
+
+        public Builder id(final String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder name(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder type(final String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder field(final String field) {
+            this.field = field;
+            return this;
+        }
+
+        public Builder previousValue(final String previousValue) {
+            this.previousValue = previousValue;
+            return this;
+        }
+
+        public Builder value(final String value) {
+            this.value = value;
+            return this;
+        }
+
+        public ConfigurationAction build() {
+            return new ConfigurationAction(this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/InvalidRevisionException.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/InvalidRevisionException.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/InvalidRevisionException.java
new file mode 100644
index 0000000..8e04f69
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/InvalidRevisionException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web;
+
+/**
+ * Exception indicating that the client has included an old revision in their
+ * request.
+ */
+@SuppressWarnings("serial")
+public class InvalidRevisionException extends RuntimeException {
+
+    public InvalidRevisionException(String message) {
+        super(message);
+    }
+
+    public InvalidRevisionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java
new file mode 100644
index 0000000..5082af2
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java
@@ -0,0 +1,99 @@
+/*
+ * 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.web;
+
+import org.apache.nifi.controller.ControllerService;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * NiFi web context providing limited access to dataflow configuration for
+ * component custom UIs.
+ */
+public interface NiFiWebConfigurationContext {
+
+    /**
+     * @param serviceIdentifier of the controller service
+     * @param componentId the id of the component that is referencing the controller service
+     * @return the ControllerService for the specified identifier. If a
+     * corresponding service cannot be found, null is returned. If this NiFi is
+     * clustered, the only services available will be those those availability
+     * is NCM only
+     */
+    ControllerService getControllerService(String serviceIdentifier, String componentId);
+
+    /**
+     * Provides a mechanism for custom UIs to save actions to appear in NiFi
+     * configuration history. Note all fields within each Action must be
+     * populated. Null values will result in a failure to insert the audit
+     * record. Since the saving to these actions is separate from the actual
+     * configuration change, a failure to insert here will just generate a
+     * warning log message. The recording of these actions typically happens
+     * after a configuration change is applied. Since those changes have already
+     * been applied to the flow, we cannot revert them because of a failure to
+     * insert an audit record.
+     *
+     * @param requestContext context of the request
+     * @param actions to save
+     * @throws IllegalArgumentException When the requestContext isn't fully
+     * populated or isn't appropriate for the given request
+     */
+    void saveActions(NiFiWebRequestContext requestContext, Collection<ConfigurationAction> actions);
+
+    /**
+     * @return the current user identity. The value may be a DN, an email, a username, or any string that identities the user. Returns null if no user is found
+     */
+    String getCurrentUserIdentity();
+
+    /**
+     * Sets the annotation data for the underlying component.
+     *
+     * @param configurationContext config context
+     * @param annotationData the data
+     * @param properties component properties
+     * @return the configuration for the underlying component
+     * @throws ResourceNotFoundException if the underlying component does not
+     * exit
+     * @throws InvalidRevisionException if a revision other than the current
+     * revision is given
+     * @throws ClusterRequestException if the annotation data was unable to be
+     * set for the underlying component. This exception will only be thrown when
+     * operating in a cluster.
+     * @throws IllegalArgumentException When the requestContext isn't fully
+     * populated or isn't appropriate for the given request
+     */
+    ComponentDetails updateComponent(NiFiWebConfigurationRequestContext configurationContext, String annotationData, Map<String, String> properties)
+            throws ResourceNotFoundException, InvalidRevisionException, ClusterRequestException;
+
+
+    /**
+     * Gets the details for the underlying component (including configuration,
+     * validation errors, and annotation data).
+     *
+     * @param requestContext context of request
+     * @return the configuration for the underlying component
+     * @throws ResourceNotFoundException if the underlying component does not
+     * exit
+     * @throws ClusterRequestException if the underlying component was unable to
+     * be retrieved from the cluster. This exception will only be thrown when
+     * operating in a cluster.
+     * @throws IllegalArgumentException When the requestContext isn't fully
+     * populated or isn't appropriate for the given request
+     */
+    ComponentDetails getComponentDetails(NiFiWebRequestContext requestContext) throws ResourceNotFoundException, ClusterRequestException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationRequestContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationRequestContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationRequestContext.java
new file mode 100644
index 0000000..c75d9dc
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationRequestContext.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.web;
+
+/**
+ * Contextual details required to make a configuration request from a UI
+ * extension.
+ */
+public interface NiFiWebConfigurationRequestContext extends NiFiWebRequestContext {
+
+    /**
+     * The revision to include in the request.
+     *
+     * @return the revision
+     */
+    Revision getRevision();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java
new file mode 100644
index 0000000..9dd44ab
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.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.web;
+
+/**
+ * Contextual details required to make a request from a UI extension.
+ */
+public interface NiFiWebRequestContext {
+
+    /**
+     * @return the type of UI extension is making the request
+     */
+    UiExtensionType getExtensionType();
+
+    /**
+     * The request protocol scheme (http or https). When scheme is https, the
+     * X509Certificate can be used for subsequent remote requests.
+     *
+     * @return the protocol scheme
+     */
+    String getScheme();
+
+    /**
+     * The id of the component.
+     *
+     * @return the ID
+     */
+    String getId();
+
+    /**
+     * Returns the proxied entities chain. The format of the chain is as
+     * follows:
+     *
+     * <code>
+     * &lt;CN=original-proxied-entity&gt;&lt;CN=first-proxy&gt;&lt;CN=second-proxy&gt;...
+     * </code>
+     *
+     * @return the proxied entities chain or null if no chain
+     */
+    String getProxiedEntitiesChain();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/ResourceNotFoundException.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/ResourceNotFoundException.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/ResourceNotFoundException.java
new file mode 100644
index 0000000..b2f2e57
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/ResourceNotFoundException.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.web;
+
+/**
+ * Exception indicating that the desired resource was not found.
+ */
+@SuppressWarnings("serial")
+public class ResourceNotFoundException extends RuntimeException {
+
+    public ResourceNotFoundException(String message) {
+        super(message);
+    }
+
+    public ResourceNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/Revision.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/Revision.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/Revision.java
new file mode 100644
index 0000000..d768faa
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/Revision.java
@@ -0,0 +1,129 @@
+/*
+ * 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.web;
+
+import java.io.Serializable;
+
+/**
+ * A model object representing a revision. Equality is defined as matching
+ * component ID and either a matching version number or matching non-empty client IDs.
+ *
+ * @Immutable
+ * @Threadsafe
+ */
+public class Revision implements Serializable {
+    private static final long serialVersionUID = 988658790374170022L;
+
+    /**
+     * the version number
+     */
+    private final Long version;
+
+    /**
+     * the client ID
+     */
+    private final String clientId;
+
+    /**
+     * the ID of the component that this revision belongs to, or <code>null</code> if
+     * the revision is not attached to any component but rather is attached to the entire
+     * data flow.
+     */
+    private final String componentId;
+
+    public Revision(Long version, String clientId, String componentId) {
+        if (version == null) {
+            throw new IllegalArgumentException("The revision must be specified.");
+        }
+        if (componentId == null) {
+            throw new IllegalArgumentException("The componentId must be specified.");
+        }
+
+        this.version = version;
+        this.clientId = clientId;
+        this.componentId = componentId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public Long getVersion() {
+        return version;
+    }
+
+    public String getComponentId() {
+        return componentId;
+    }
+
+    /**
+     * Returns a new Revision that has the same Client ID and Component ID as this one
+     * but with a larger version
+     *
+     * @return the updated Revision
+     */
+    public Revision incrementRevision(final String clientId) {
+        return new Revision(version + 1, clientId, componentId);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+
+        if ((obj instanceof Revision) == false) {
+            return false;
+        }
+
+        Revision thatRevision = (Revision) obj;
+        // ensure that component ID's are the same (including null)
+        if (thatRevision.getComponentId() == null && getComponentId() != null) {
+            return false;
+        }
+        if (thatRevision.getComponentId() != null && getComponentId() == null) {
+            return false;
+        }
+        if (thatRevision.getComponentId() != null && !thatRevision.getComponentId().equals(getComponentId())) {
+            return false;
+        }
+
+        if (this.version != null && this.version.equals(thatRevision.version)) {
+            return true;
+        } else {
+            return clientId != null && !clientId.trim().isEmpty() && clientId.equals(thatRevision.getClientId());
+        }
+
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 59 * hash + (this.componentId != null ? this.componentId.hashCode() : 0);
+        hash = 59 * hash + (this.version != null ? this.version.hashCode() : 0);
+        hash = 59 * hash + (this.clientId != null ? this.clientId.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public String toString() {
+        return "[" + version + ", " + clientId + ", " + componentId + ']';
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.java
new file mode 100644
index 0000000..e3b0f8a
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/UiExtensionType.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.web;
+
+/**
+ * Types of UI extensions. Since a UI extension could support multiple types of
+ * custom UIs it will need to include the type so the framework can appropriate
+ * understand and process the request (recording actions in the audit database,
+ * replicating a request throughout the cluster to the appropriate endpoints,
+ * etc).
+ */
+public enum UiExtensionType {
+
+    ContentViewer,
+    ProcessorConfiguration,
+    ControllerServiceConfiguration,
+    ReportingTaskConfiguration
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/main/java/org/apache/nifi/web/ViewableContent.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/ViewableContent.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/ViewableContent.java
new file mode 100644
index 0000000..d506b5d
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/ViewableContent.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface for obtaining content from the NiFi content repository.
+ */
+public interface ViewableContent {
+
+    public static final String CONTENT_REQUEST_ATTRIBUTE = "org.apache.nifi.web.content";
+
+    public enum DisplayMode {
+
+        Original,
+        Formatted,
+        Hex;
+    }
+
+    /**
+     * @return stream to the viewable content. The data stream can only be read once
+     * so an extension can call this method or getContent
+     */
+    InputStream getContentStream();
+
+    /**
+     * @return the content as a string. The data stream can only be read once so an
+     * extension can call this method or getContentStream
+     * @throws java.io.IOException if unable to read content
+     */
+    String getContent() throws IOException;
+
+    /**
+     * @return the desired display mode. If the mode is Hex the framework will
+     * handle generating the mark up. The only values that an extension will see
+     * is Original or Formatted
+     */
+    DisplayMode getDisplayMode();
+
+    /**
+     * @return contents file name
+     */
+    String getFileName();
+
+    /**
+     * @return mime type of the content, value is lowercase and stripped of all parameters if there were any
+     */
+    String getContentType();
+
+    /**
+     * @return unchanged mime type of the content
+     */
+    String getRawContentType();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
new file mode 100644
index 0000000..b173d3d
--- /dev/null
+++ b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
@@ -0,0 +1,447 @@
+/*
+ * 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.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+public class TestAbstractPolicyBasedAuthorizer {
+
+    static final Resource TEST_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return "1";
+        }
+
+        @Override
+        public String getName() {
+            return "resource1";
+        }
+    };
+
+    @Test
+    public void testApproveBasedOnUser() {
+        AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
+        when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
+
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser(userIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        when(usersAndAccessPolicies.getUser(userIdentity)).thenReturn(user);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        assertEquals(AuthorizationResult.approved(), authorizer.authorize(request));
+    }
+
+    @Test
+    public void testApprovedBasedOnGroup() {
+        AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
+        when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
+
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+        final String groupIdentifier = "groupIdentifier1";
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addGroup(groupIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        when(usersAndAccessPolicies.getUser(userIdentity)).thenReturn(user);
+
+        final Group group = new Group.Builder()
+                .identifier(groupIdentifier)
+                .name(groupIdentifier)
+                .addUser(user.getIdentifier())
+                .build();
+
+        when(usersAndAccessPolicies.getGroups(userIdentity)).thenReturn(Collections.singleton(group));
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        assertEquals(AuthorizationResult.approved(), authorizer.authorize(request));
+    }
+
+    @Test
+    public void testDeny() {
+        AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
+        when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
+
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser("NOT_USER_1")
+                .action(RequestAction.READ)
+                .build();
+
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        when(usersAndAccessPolicies.getUser(userIdentity)).thenReturn(user);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        assertEquals(AuthorizationResult.denied().getResult(), authorizer.authorize(request).getResult());
+    }
+
+    @Test
+    public void testResourceNotFound() {
+        AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        UsersAndAccessPolicies usersAndAccessPolicies = Mockito.mock(UsersAndAccessPolicies.class);
+        when(authorizer.getUsersAndAccessPolicies()).thenReturn(usersAndAccessPolicies);
+
+        when(usersAndAccessPolicies.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity("userIdentity")
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        assertEquals(AuthorizationResult.resourceNotFound(), authorizer.authorize(request));
+    }
+
+    @Test
+    public void testGetFingerprint() {
+        // create the users, groups, and policies
+
+        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
+        User user2 = new User.Builder().identifier("user-id-2").identity("user-2").build();
+
+        Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").addUser(user1.getIdentifier()).build();
+        Group group2 = new Group.Builder().identifier("group-id-2").name("group-2").addUser(user2.getIdentifier()).build();
+
+        AccessPolicy policy1 = new AccessPolicy.Builder()
+                .identifier("policy-id-1")
+                .resource("resource1")
+                .action(RequestAction.READ)
+                .addUser(user1.getIdentifier())
+                .addUser(user2.getIdentifier())
+                .build();
+
+        AccessPolicy policy2 = new AccessPolicy.Builder()
+                .identifier("policy-id-2")
+                .resource("resource2")
+                .action(RequestAction.READ)
+                .addGroup(group1.getIdentifier())
+                .addGroup(group2.getIdentifier())
+                .addUser(user1.getIdentifier())
+                .addUser(user2.getIdentifier())
+                .build();
+
+        // create the first Authorizer
+
+        Set<Group> groups1 = new LinkedHashSet<>();
+        groups1.add(group1);
+        groups1.add(group2);
+
+        Set<User> users1 = new LinkedHashSet<>();
+        users1.add(user1);
+        users1.add(user2);
+
+        Set<AccessPolicy> policies1 = new LinkedHashSet<>();
+        policies1.add(policy1);
+        policies1.add(policy2);
+
+        AbstractPolicyBasedAuthorizer authorizer1 = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        when(authorizer1.getGroups()).thenReturn(groups1);
+        when(authorizer1.getUsers()).thenReturn(users1);
+        when(authorizer1.getAccessPolicies()).thenReturn(policies1);
+
+        // create the second Authorizer
+
+        Set<Group> groups2 = new LinkedHashSet<>();
+        groups2.add(group2);
+        groups2.add(group1);
+
+        Set<User> users2 = new LinkedHashSet<>();
+        users2.add(user2);
+        users2.add(user1);
+
+        Set<AccessPolicy> policies2 = new LinkedHashSet<>();
+        policies2.add(policy2);
+        policies2.add(policy1);
+
+        AbstractPolicyBasedAuthorizer authorizer2 = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        when(authorizer2.getGroups()).thenReturn(groups2);
+        when(authorizer2.getUsers()).thenReturn(users2);
+        when(authorizer2.getAccessPolicies()).thenReturn(policies2);
+
+        // compare the fingerprints
+
+        assertEquals(authorizer1.getFingerprint(), authorizer2.getFingerprint());
+
+        System.out.println(authorizer1.getFingerprint());
+    }
+
+    @Test
+    public void testInheritFingerprint() {
+
+        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
+        User user2 = new User.Builder().identifier("user-id-2").identity("user-2").build();
+
+        Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").addUser(user1.getIdentifier()).build();
+        Group group2 = new Group.Builder().identifier("group-id-2").name("group-2").build();
+
+        AccessPolicy policy1 = new AccessPolicy.Builder()
+                .identifier("policy-id-1")
+                .resource("resource1")
+                .action(RequestAction.READ)
+                .addUser(user1.getIdentifier())
+                .addUser(user2.getIdentifier())
+                .build();
+
+        AccessPolicy policy2 = new AccessPolicy.Builder()
+                .identifier("policy-id-2")
+                .resource("resource2")
+                .action(RequestAction.READ)
+                .addGroup(group1.getIdentifier())
+                .addGroup(group2.getIdentifier())
+                .addUser(user1.getIdentifier())
+                .addUser(user2.getIdentifier())
+                .build();
+
+        // create the first Authorizer
+
+        Set<Group> groups1 = new LinkedHashSet<>();
+        groups1.add(group1);
+        groups1.add(group2);
+
+        Set<User> users1 = new LinkedHashSet<>();
+        users1.add(user1);
+        users1.add(user2);
+
+        Set<AccessPolicy> policies1 = new LinkedHashSet<>();
+        policies1.add(policy1);
+        policies1.add(policy2);
+
+        AbstractPolicyBasedAuthorizer authorizer1 = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        when(authorizer1.getGroups()).thenReturn(groups1);
+        when(authorizer1.getUsers()).thenReturn(users1);
+        when(authorizer1.getAccessPolicies()).thenReturn(policies1);
+
+        final String fingerprint1 = authorizer1.getFingerprint();
+
+        // make a second authorizer using the memory-backed implementation so we can inherit the fingerprint
+        // and then compute a new fingerprint to compare them
+        AbstractPolicyBasedAuthorizer authorizer2 = new MemoryPolicyBasedAuthorizer();
+        authorizer2.inheritFingerprint(fingerprint1);
+
+        // computer the fingerprint of the second authorizer and it should be the same as the first
+        final String fingerprint2 = authorizer2.getFingerprint();
+        assertEquals(fingerprint1, fingerprint2);
+
+        // all the sets should be equal now after inheriting
+        assertEquals(authorizer1.getUsers(), authorizer2.getUsers());
+        assertEquals(authorizer1.getGroups(), authorizer2.getGroups());
+        assertEquals(authorizer1.getAccessPolicies(), authorizer2.getAccessPolicies());
+    }
+
+    @Test
+    public void testEmptyAuthorizer() {
+        AbstractPolicyBasedAuthorizer authorizer = Mockito.mock(AbstractPolicyBasedAuthorizer.class);
+        when(authorizer.getGroups()).thenReturn(new HashSet<Group>());
+        when(authorizer.getUsers()).thenReturn(new HashSet<User>());
+        when(authorizer.getAccessPolicies()).thenReturn(new HashSet<AccessPolicy>());
+
+        final String fingerprint = authorizer.getFingerprint();
+        Assert.assertNotNull(fingerprint);
+        Assert.assertTrue(fingerprint.length() > 0);
+    }
+
+    /**
+     * An AbstractPolicyBasedAuthorizer that stores everything in memory.
+     */
+    private static final class MemoryPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer {
+
+        private Set<Group> groups = new HashSet<>();
+        private Set<User> users = new HashSet<>();
+        private Set<AccessPolicy> policies = new HashSet<>();
+
+        @Override
+        public Group addGroup(Group group) throws AuthorizationAccessException {
+            groups.add(group);
+            return group;
+        }
+
+        @Override
+        public Group getGroup(String identifier) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Group updateGroup(Group group) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Group deleteGroup(Group group) throws AuthorizationAccessException {
+            groups.remove(group);
+            return group;
+        }
+
+        @Override
+        public Set<Group> getGroups() throws AuthorizationAccessException {
+            return groups;
+        }
+
+        @Override
+        public User addUser(User user) throws AuthorizationAccessException {
+            users.add(user);
+            return user;
+        }
+
+        @Override
+        public User getUser(String identifier) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public User updateUser(User user) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public User deleteUser(User user) throws AuthorizationAccessException {
+            users.remove(user);
+            return user;
+        }
+
+        @Override
+        public Set<User> getUsers() throws AuthorizationAccessException {
+            return users;
+        }
+
+        @Override
+        protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+            policies.add(accessPolicy);
+            return accessPolicy;
+        }
+
+        @Override
+        public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+            return policies;
+        }
+
+        @Override
+        public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+
+        }
+
+        @Override
+        public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+
+        }
+
+        @Override
+        public void preDestruction() throws AuthorizerDestructionException {
+
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAccessPolicy.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAccessPolicy.java b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAccessPolicy.java
new file mode 100644
index 0000000..c8a4452
--- /dev/null
+++ b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAccessPolicy.java
@@ -0,0 +1,240 @@
+/*
+ * 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.authorization;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class TestAccessPolicy {
+
+    static final String TEST_RESOURCE = "1";
+
+    @Test
+    public void testSimpleCreation() {
+        final String identifier = "1";
+        final String user1 = "user1";
+        final String user2 = "user2";
+        final RequestAction action = RequestAction.READ;
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier(identifier)
+                .resource(TEST_RESOURCE)
+                .addUser(user1)
+                .addUser(user2)
+                .action(action)
+                .build();
+
+        assertEquals(identifier, policy.getIdentifier());
+
+        assertNotNull(policy.getResource());
+        assertEquals(TEST_RESOURCE, policy.getResource());
+
+        assertNotNull(policy.getUsers());
+        assertEquals(2, policy.getUsers().size());
+        assertTrue(policy.getUsers().contains(user1));
+        assertTrue(policy.getUsers().contains(user2));
+
+        assertNotNull(policy.getAction());
+        assertEquals(RequestAction.READ, policy.getAction());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingIdentifier() {
+        new AccessPolicy.Builder()
+                .resource(TEST_RESOURCE)
+                .addUser("user1")
+                .action(RequestAction.READ)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingResource() {
+        new AccessPolicy.Builder()
+                .identifier("1")
+                .addUser("user1")
+                .action(RequestAction.READ)
+                .build();
+    }
+
+    @Test
+    public void testMissingUsersAndGroups() {
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .build();
+
+        assertNotNull(policy);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingActions() {
+        new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE)
+                .addUser("user1")
+                .build();
+    }
+
+    @Test
+    public void testFromPolicy() {
+        final String identifier = "1";
+        final String user1 = "user1";
+        final String user2 = "user2";
+        final String group1 = "group1";
+        final String group2 = "group2";
+        final RequestAction action = RequestAction.READ;
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier(identifier)
+                .resource(TEST_RESOURCE)
+                .addUser(user1)
+                .addUser(user2)
+                .addGroup(group1)
+                .addGroup(group2)
+                .action(action)
+                .build();
+
+        assertEquals(identifier, policy.getIdentifier());
+
+        assertNotNull(policy.getResource());
+        assertEquals(TEST_RESOURCE, policy.getResource());
+
+        assertNotNull(policy.getUsers());
+        assertEquals(2, policy.getUsers().size());
+        assertTrue(policy.getUsers().contains(user1));
+        assertTrue(policy.getUsers().contains(user2));
+
+        assertNotNull(policy.getGroups());
+        assertEquals(2, policy.getGroups().size());
+        assertTrue(policy.getGroups().contains(group1));
+        assertTrue(policy.getGroups().contains(group2));
+
+        assertNotNull(policy.getAction());
+        assertEquals(RequestAction.READ, policy.getAction());
+
+        final AccessPolicy policy2 = new AccessPolicy.Builder(policy).build();
+        assertEquals(policy.getIdentifier(), policy2.getIdentifier());
+        assertEquals(policy.getResource(), policy2.getResource());
+        assertEquals(policy.getUsers(), policy2.getUsers());
+        assertEquals(policy.getAction(), policy2.getAction());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testFromPolicyAndChangeIdentifier() {
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE)
+                .addUser("user1")
+                .action(RequestAction.READ)
+                .build();
+
+        new AccessPolicy.Builder(policy).identifier("2").build();
+    }
+
+    @Test
+    public void testAddRemoveClearUsers() {
+        final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE)
+                .addUser("user1")
+                .action(RequestAction.READ);
+
+        final AccessPolicy policy1 = builder.build();
+        assertEquals(1, policy1.getUsers().size());
+        assertTrue(policy1.getUsers().contains("user1"));
+
+        final Set<String> moreEntities = new HashSet<>();
+        moreEntities.add("user2");
+        moreEntities.add("user3");
+        moreEntities.add("user4");
+
+        final AccessPolicy policy2 = builder.addUsers(moreEntities).build();
+        assertEquals(4, policy2.getUsers().size());
+        assertTrue(policy2.getUsers().contains("user1"));
+        assertTrue(policy2.getUsers().contains("user2"));
+        assertTrue(policy2.getUsers().contains("user3"));
+        assertTrue(policy2.getUsers().contains("user4"));
+
+        final AccessPolicy policy3 = builder.removeUser("user3").build();
+        assertEquals(3, policy3.getUsers().size());
+        assertTrue(policy3.getUsers().contains("user1"));
+        assertTrue(policy3.getUsers().contains("user2"));
+        assertTrue(policy3.getUsers().contains("user4"));
+
+        final Set<String> removeEntities = new HashSet<>();
+        removeEntities.add("user1");
+        removeEntities.add("user4");
+
+        final AccessPolicy policy4 = builder.removeUsers(removeEntities).build();
+        assertEquals(1, policy4.getUsers().size());
+        assertTrue(policy4.getUsers().contains("user2"));
+
+
+        final AccessPolicy policy5 = builder.clearUsers().build();
+        assertEquals(0, policy5.getUsers().size());
+    }
+
+    @Test
+    public void testAddRemoveClearGroups() {
+        final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE)
+                .addGroup("group1")
+                .action(RequestAction.READ);
+
+        final AccessPolicy policy1 = builder.build();
+        assertEquals(1, policy1.getGroups().size());
+        assertTrue(policy1.getGroups().contains("group1"));
+
+        final Set<String> moreGroups = new HashSet<>();
+        moreGroups.add("group2");
+        moreGroups.add("group3");
+        moreGroups.add("group4");
+
+        final AccessPolicy policy2 = builder.addGroups(moreGroups).build();
+        assertEquals(4, policy2.getGroups().size());
+        assertTrue(policy2.getGroups().contains("group1"));
+        assertTrue(policy2.getGroups().contains("group2"));
+        assertTrue(policy2.getGroups().contains("group3"));
+        assertTrue(policy2.getGroups().contains("group4"));
+
+        final AccessPolicy policy3 = builder.removeGroup("group3").build();
+        assertEquals(3, policy3.getGroups().size());
+        assertTrue(policy3.getGroups().contains("group1"));
+        assertTrue(policy3.getGroups().contains("group2"));
+        assertTrue(policy3.getGroups().contains("group4"));
+
+        final Set<String> removeGroups = new HashSet<>();
+        removeGroups.add("group1");
+        removeGroups.add("group4");
+
+        final AccessPolicy policy4 = builder.removeGroups(removeGroups).build();
+        assertEquals(1, policy4.getGroups().size());
+        assertTrue(policy4.getGroups().contains("group2"));
+
+        final AccessPolicy policy5 = builder.clearGroups().build();
+        assertEquals(0, policy5.getUsers().size());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestGroup.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestGroup.java b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestGroup.java
new file mode 100644
index 0000000..8ccdba5
--- /dev/null
+++ b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestGroup.java
@@ -0,0 +1,169 @@
+/*
+ * 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.authorization;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class TestGroup {
+
+    @Test
+    public void testSimpleCreation() {
+        final String id = "1";
+        final String name = "group1";
+        final String user1 = "user1";
+        final String user2 = "user2";
+
+        final Group group = new Group.Builder()
+                .identifier(id)
+                .name(name)
+                .addUser(user1)
+                .addUser(user2)
+                .build();
+
+        assertEquals(id, group.getIdentifier());
+        assertEquals(name, group.getName());
+
+        assertNotNull(group.getUsers());
+        assertEquals(2, group.getUsers().size());
+        assertTrue(group.getUsers().contains(user1));
+        assertTrue(group.getUsers().contains(user2));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingId() {
+        new Group.Builder()
+                .name("group1")
+                .addUser("user1")
+                .addUser("user2")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingName() {
+        new Group.Builder()
+                .identifier("1")
+                .addUser("user1")
+                .addUser("user2")
+                .build();
+    }
+
+    @Test
+    public void testMissingUsers() {
+        final String id = "1";
+        final String name = "group1";
+
+        final Group group = new Group.Builder()
+                .identifier(id)
+                .name(name)
+                .build();
+
+        assertEquals(id, group.getIdentifier());
+        assertEquals(name, group.getName());
+
+        assertNotNull(group.getUsers());
+        assertEquals(0, group.getUsers().size());
+    }
+
+    @Test
+    public void testFromGroup() {
+        final String id = "1";
+        final String name = "group1";
+        final String user1 = "user1";
+        final String user2 = "user2";
+
+        final Group group1 = new Group.Builder()
+                .identifier(id)
+                .name(name)
+                .addUser(user1)
+                .addUser(user2)
+                .build();
+
+        assertEquals(id, group1.getIdentifier());
+        assertEquals(name, group1.getName());
+
+        assertNotNull(group1.getUsers());
+        assertEquals(2, group1.getUsers().size());
+        assertTrue(group1.getUsers().contains(user1));
+        assertTrue(group1.getUsers().contains(user2));
+
+        final Group group2 = new Group.Builder(group1).build();
+        assertEquals(group1.getIdentifier(), group2.getIdentifier());
+        assertEquals(group1.getName(), group2.getName());
+        assertEquals(group1.getUsers(), group2.getUsers());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testFromGroupAndChangeIdentifier() {
+        final Group group1 = new Group.Builder()
+                .identifier("1")
+                .name("group1")
+                .addUser("user1")
+                .build();
+
+        new Group.Builder(group1).identifier("2").build();
+    }
+
+    @Test
+    public void testAddRemoveClearUsers() {
+        final Group.Builder builder = new Group.Builder()
+                .identifier("1")
+                .name("group1")
+                .addUser("user1");
+
+        final Group group1 = builder.build();
+        assertNotNull(group1.getUsers());
+        assertEquals(1, group1.getUsers().size());
+        assertTrue(group1.getUsers().contains("user1"));
+
+        final Set<String> moreUsers = new HashSet<>();
+        moreUsers.add("user2");
+        moreUsers.add("user3");
+        moreUsers.add("user4");
+
+        final Group group2 = builder.addUsers(moreUsers).build();
+        assertEquals(4, group2.getUsers().size());
+        assertTrue(group2.getUsers().contains("user1"));
+        assertTrue(group2.getUsers().contains("user2"));
+        assertTrue(group2.getUsers().contains("user3"));
+        assertTrue(group2.getUsers().contains("user4"));
+
+        final Group group3 = builder.removeUser("user2").build();
+        assertEquals(3, group3.getUsers().size());
+        assertTrue(group3.getUsers().contains("user1"));
+        assertTrue(group3.getUsers().contains("user3"));
+        assertTrue(group3.getUsers().contains("user4"));
+
+        final Set<String> removeUsers = new HashSet<>();
+        removeUsers.add("user1");
+        removeUsers.add("user4");
+
+        final Group group4 = builder.removeUsers(removeUsers).build();
+        assertEquals(1, group4.getUsers().size());
+        assertTrue(group4.getUsers().contains("user3"));
+
+        final Group group5 = builder.clearUsers().build();
+        assertEquals(0, group5.getUsers().size());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestUser.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestUser.java b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestUser.java
new file mode 100644
index 0000000..04b1ab1
--- /dev/null
+++ b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestUser.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.authorization;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestUser {
+
+    @Test
+    public void testSimpleCreation() {
+        final String identifier = "1";
+        final String identity = "user1";
+        final String group1 = "group1";
+        final String group2 = "group2";
+
+        final User user = new User.Builder()
+                .identifier(identifier)
+                .identity(identity)
+                .build();
+
+        assertEquals(identifier, user.getIdentifier());
+        assertEquals(identity, user.getIdentity());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingIdentifier() {
+        new User.Builder()
+                .identity("user1")
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMissingIdentity() {
+        new User.Builder()
+                .identifier("1")
+                .build();
+    }
+
+    @Test
+    public void testFromUser() {
+        final String identifier = "1";
+        final String identity = "user1";
+        final String group1 = "group1";
+        final String group2 = "group2";
+
+        final User user = new User.Builder()
+                .identifier(identifier)
+                .identity(identity)
+                .build();
+
+        assertEquals(identifier, user.getIdentifier());
+        assertEquals(identity, user.getIdentity());
+
+        final User user2 = new User.Builder(user).build();
+        assertEquals(user.getIdentifier(), user2.getIdentifier());
+        assertEquals(user.getIdentity(), user2.getIdentity());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testFromUserAndChangeIdentifier() {
+        final User user = new User.Builder()
+                .identifier("1")
+                .identity("user1")
+                .build();
+
+        new User.Builder(user).identifier("2").build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-framework-api/src/test/java/org/apache/nifi/web/TestRevision.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/web/TestRevision.java b/nifi-framework-api/src/test/java/org/apache/nifi/web/TestRevision.java
new file mode 100644
index 0000000..6d513fd
--- /dev/null
+++ b/nifi-framework-api/src/test/java/org/apache/nifi/web/TestRevision.java
@@ -0,0 +1,61 @@
+/*
+ * 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.web;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ */
+public class TestRevision {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullVersion() throws Exception {
+        new Revision(null, "client-id", "component-id");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullComponentId() throws Exception {
+        new Revision(0l, "client-id", null);
+    }
+
+    @Test
+    public void testIncrementRevision() throws Exception {
+        final String clientId = "client-id";
+        final String componentId = "component-id";
+        final Revision revision = new Revision(0l, clientId, componentId);
+        final Revision updatedRevision = revision.incrementRevision(clientId);
+        assertEquals(1, updatedRevision.getVersion().longValue());
+        assertEquals(clientId, updatedRevision.getClientId());
+        assertEquals(componentId, updatedRevision.getComponentId());
+    }
+
+    @Test
+    public void testIncrementRevisionNewClient() throws Exception {
+        final String clientId = "client-id";
+        final String newClientId = "new-client-id";
+        final String componentId = "component-id";
+        final Revision revision = new Revision(0l, clientId, componentId);
+        final Revision updatedRevision = revision.incrementRevision(newClientId);
+        assertEquals(1, updatedRevision.getVersion().longValue());
+        assertEquals(newClientId, updatedRevision.getClientId());
+        assertEquals(componentId, updatedRevision.getComponentId());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-mock/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-mock/pom.xml b/nifi-mock/pom.xml
index dd15fef..53f29ff 100644
--- a/nifi-mock/pom.xml
+++ b/nifi-mock/pom.xml
@@ -28,6 +28,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-utils</artifactId>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
----------------------------------------------------------------------
diff --git a/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java b/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
deleted file mode 100644
index dc71ed5..0000000
--- a/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceEventRepository.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.provenance;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.nifi.authorization.Authorizer;
-import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.events.EventReporter;
-import org.apache.nifi.provenance.lineage.ComputeLineageSubmission;
-import org.apache.nifi.provenance.search.Query;
-import org.apache.nifi.provenance.search.QuerySubmission;
-import org.apache.nifi.provenance.search.SearchableField;
-
-public class MockProvenanceEventRepository implements ProvenanceEventRepository {
-
-    private final List<ProvenanceEventRecord> records = new ArrayList<>();
-    private final AtomicLong idGenerator = new AtomicLong(0L);
-
-    @Override
-    public void registerEvents(final Iterable<ProvenanceEventRecord> events) {
-        for (final ProvenanceEventRecord event : events) {
-            registerEvent(event);
-        }
-    }
-
-    @Override
-    public void registerEvent(final ProvenanceEventRecord event) {
-        final StandardProvenanceEventRecord newRecord;
-        if (event instanceof StandardProvenanceEventRecord) {
-            newRecord = (StandardProvenanceEventRecord) event;
-        } else {
-            newRecord = new StandardProvenanceEventRecord.Builder().fromEvent(event).build();
-        }
-        newRecord.setEventId(idGenerator.getAndIncrement());
-
-        records.add(newRecord);
-    }
-
-    @Override
-    public void initialize(EventReporter eventReporter, Authorizer authorizer, ProvenanceAuthorizableFactory resourceFactory) throws IOException {
-
-    }
-
-    @Override
-    public List<ProvenanceEventRecord> getEvents(long firstRecordId, int maxRecords) throws IOException {
-        if (firstRecordId > records.size()) {
-            return Collections.emptyList();
-        }
-
-        return records.subList((int) firstRecordId, Math.min(records.size(), (int) (firstRecordId + maxRecords)));
-    }
-
-    @Override
-    public List<ProvenanceEventRecord> getEvents(long firstRecordId, int maxRecords, NiFiUser user) throws IOException {
-        return getEvents(firstRecordId, maxRecords);
-    }
-
-    @Override
-    public Long getMaxEventId() {
-        return Long.valueOf(records.size() - 1);
-    }
-
-    @Override
-    public QuerySubmission submitQuery(Query query, NiFiUser user) {
-        throw new UnsupportedOperationException("MockProvenanceEventRepository does not support querying");
-    }
-
-    @Override
-    public QuerySubmission retrieveQuerySubmission(String queryIdentifier, NiFiUser user) {
-        throw new UnsupportedOperationException("MockProvenanceEventRepository does not support querying");
-    }
-
-    @Override
-    public ComputeLineageSubmission submitLineageComputation(String flowFileUuid, NiFiUser user) {
-        throw new UnsupportedOperationException("MockProvenanceEventRepository does not support Lineage Computation");
-    }
-
-    @Override
-    public ComputeLineageSubmission retrieveLineageSubmission(String lineageIdentifier, NiFiUser user) {
-        throw new UnsupportedOperationException("MockProvenanceEventRepository does not support Lineage Computation");
-    }
-
-    @Override
-    public ProvenanceEventRecord getEvent(long id, NiFiUser user) throws IOException {
-        if (id > records.size()) {
-            return null;
-        }
-
-        return records.get((int) id);
-    }
-
-    @Override
-    public ComputeLineageSubmission submitExpandParents(long eventId, NiFiUser user) {
-        throw new UnsupportedOperationException("MockProvenanceEventRepository does not support Lineage Computation");
-    }
-
-    @Override
-    public ComputeLineageSubmission submitExpandChildren(long eventId, NiFiUser user) {
-        throw new UnsupportedOperationException("MockProvenanceEventRepository does not support Lineage Computation");
-    }
-
-    @Override
-    public void close() throws IOException {
-    }
-
-    @Override
-    public List<SearchableField> getSearchableFields() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public List<SearchableField> getSearchableAttributes() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public ProvenanceEventBuilder eventBuilder() {
-        return new StandardProvenanceEventRecord.Builder();
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceRepository.java
----------------------------------------------------------------------
diff --git a/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceRepository.java b/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceRepository.java
new file mode 100644
index 0000000..a13a338
--- /dev/null
+++ b/nifi-mock/src/main/java/org/apache/nifi/provenance/MockProvenanceRepository.java
@@ -0,0 +1,149 @@
+/*
+ * 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.provenance;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.events.EventReporter;
+import org.apache.nifi.provenance.lineage.ComputeLineageSubmission;
+import org.apache.nifi.provenance.search.Query;
+import org.apache.nifi.provenance.search.QuerySubmission;
+import org.apache.nifi.provenance.search.SearchableField;
+
+public class MockProvenanceRepository implements ProvenanceRepository {
+
+    private final List<ProvenanceEventRecord> records = new ArrayList<>();
+    private final AtomicLong idGenerator = new AtomicLong(0L);
+
+    @Override
+    public void registerEvents(final Iterable<ProvenanceEventRecord> events) {
+        for (final ProvenanceEventRecord event : events) {
+            registerEvent(event);
+        }
+    }
+
+    @Override
+    public void registerEvent(final ProvenanceEventRecord event) {
+        final StandardProvenanceEventRecord newRecord;
+        if (event instanceof StandardProvenanceEventRecord) {
+            newRecord = (StandardProvenanceEventRecord) event;
+        } else {
+            newRecord = new StandardProvenanceEventRecord.Builder().fromEvent(event).build();
+        }
+        newRecord.setEventId(idGenerator.getAndIncrement());
+
+        records.add(newRecord);
+    }
+
+    @Override
+    public void initialize(EventReporter eventReporter, Authorizer authorizer, ProvenanceAuthorizableFactory resourceFactory) throws IOException {
+
+    }
+
+    @Override
+    public List<ProvenanceEventRecord> getEvents(long firstRecordId, int maxRecords) throws IOException {
+        if (firstRecordId > records.size()) {
+            return Collections.emptyList();
+        }
+
+        return records.subList((int) firstRecordId, Math.min(records.size(), (int) (firstRecordId + maxRecords)));
+    }
+
+    @Override
+    public List<ProvenanceEventRecord> getEvents(long firstRecordId, int maxRecords, NiFiUser user) throws IOException {
+        return getEvents(firstRecordId, maxRecords);
+    }
+
+    @Override
+    public Long getMaxEventId() {
+        return Long.valueOf(records.size() - 1);
+    }
+
+    @Override
+    public ProvenanceEventRecord getEvent(long id) throws IOException {
+        return null;
+    }
+
+    @Override
+    public QuerySubmission submitQuery(Query query, NiFiUser user) {
+        throw new UnsupportedOperationException("MockProvenanceRepository does not support querying");
+    }
+
+    @Override
+    public QuerySubmission retrieveQuerySubmission(String queryIdentifier, NiFiUser user) {
+        throw new UnsupportedOperationException("MockProvenanceRepository does not support querying");
+    }
+
+    @Override
+    public ComputeLineageSubmission submitLineageComputation(String flowFileUuid, NiFiUser user) {
+        throw new UnsupportedOperationException("MockProvenanceRepository does not support Lineage Computation");
+    }
+
+    @Override
+    public ComputeLineageSubmission retrieveLineageSubmission(String lineageIdentifier, NiFiUser user) {
+        throw new UnsupportedOperationException("MockProvenanceRepository does not support Lineage Computation");
+    }
+
+    @Override
+    public ProvenanceEventRecord getEvent(long id, NiFiUser user) throws IOException {
+        if (id > records.size()) {
+            return null;
+        }
+
+        return records.get((int) id);
+    }
+
+    @Override
+    public ComputeLineageSubmission submitExpandParents(long eventId, NiFiUser user) {
+        throw new UnsupportedOperationException("MockProvenanceRepository does not support Lineage Computation");
+    }
+
+    @Override
+    public ComputeLineageSubmission submitExpandChildren(long eventId, NiFiUser user) {
+        throw new UnsupportedOperationException("MockProvenanceRepository does not support Lineage Computation");
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+
+    @Override
+    public List<SearchableField> getSearchableFields() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<SearchableField> getSearchableAttributes() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public ProvenanceEventBuilder eventBuilder() {
+        return new StandardProvenanceEventRecord.Builder();
+    }
+
+    @Override
+    public ProvenanceEventRepository getProvenanceEventRepository() {
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
----------------------------------------------------------------------
diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java b/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
index 8b3bf25..5c9a0ed 100644
--- a/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockEventAccess.java
@@ -65,7 +65,6 @@ public class MockEventAccess implements EventAccess {
         this.provenanceRecords.add(record);
     }
 
-    @Override
     public ProvenanceEventRepository getProvenanceRepository() {
         return null;
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1129706/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml
index 2aac209..5383803 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml
@@ -32,6 +32,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-framework-core-api</artifactId>
         </dependency>
         <dependency>