http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/java/org/apache/nifi/distributed/cache/server/set/SimpleSetCache.java ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/java/org/apache/nifi/distributed/cache/server/set/SimpleSetCache.java b/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/java/org/apache/nifi/distributed/cache/server/set/SimpleSetCache.java deleted file mode 100644 index 77d6481..0000000 --- a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/java/org/apache/nifi/distributed/cache/server/set/SimpleSetCache.java +++ /dev/null @@ -1,117 +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.distributed.cache.server.set; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; - -import org.apache.nifi.distributed.cache.server.EvictionPolicy; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SimpleSetCache implements SetCache { - private static final Logger logger = LoggerFactory.getLogger(SimpleSetCache.class); - - private final Map<ByteBuffer, SetCacheRecord> cache = new HashMap<>(); - private final SortedMap<SetCacheRecord, ByteBuffer> inverseCacheMap; - - private final String serviceIdentifier; - - private final int maxSize; - - public SimpleSetCache(final String serviceIdentifier, final int maxSize, final EvictionPolicy evictionPolicy) { - inverseCacheMap = new TreeMap<>(evictionPolicy.getComparator()); - this.serviceIdentifier = serviceIdentifier; - this.maxSize = maxSize; - } - - private synchronized SetCacheRecord evict() { - if ( cache.size() < maxSize ) { - return null; - } - - final SetCacheRecord recordToEvict = inverseCacheMap.firstKey(); - final ByteBuffer valueToEvict = inverseCacheMap.remove(recordToEvict); - cache.remove(valueToEvict); - - if ( logger.isDebugEnabled() ) { - logger.debug("Evicting value {} from cache", new String(valueToEvict.array(), StandardCharsets.UTF_8)); - } - - return recordToEvict; - } - - @Override - public synchronized SetCacheResult addIfAbsent(final ByteBuffer value) { - final SetCacheRecord record = cache.get(value); - if ( record == null ) { - final SetCacheRecord evicted = evict(); - final SetCacheRecord newRecord = new SetCacheRecord(value); - cache.put(value, newRecord); - inverseCacheMap.put(newRecord, value); - return new SetCacheResult(true, newRecord, evicted); - } else { - // We have to remove the record and add it again in order to cause the Map to stay sorted - inverseCacheMap.remove(record); - record.hit(); - inverseCacheMap.put(record, value); - - return new SetCacheResult(false, record, null); - } - } - - @Override - public synchronized SetCacheResult contains(final ByteBuffer value) { - final SetCacheRecord record = cache.get(value); - if ( record == null ) { - return new SetCacheResult(false, null, null); - } else { - // We have to remove the record and add it again in order to cause the Map to stay sorted - inverseCacheMap.remove(record); - record.hit(); - inverseCacheMap.put(record, value); - - return new SetCacheResult(true, record, null); - } - } - - @Override - public synchronized SetCacheResult remove(final ByteBuffer value) { - final SetCacheRecord record = cache.remove(value); - if ( record == null ) { - return new SetCacheResult(false, null, null); - } else { - inverseCacheMap.remove(record); - return new SetCacheResult(true, record, null); - } - } - - @Override - public String toString() { - return "SimpleSetCache[service id=" + serviceIdentifier + "]"; - } - - @Override - public void shutdown() throws IOException { - } -}
http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService deleted file mode 100644 index 0509c7c..0000000 --- a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService +++ /dev/null @@ -1,16 +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. -org.apache.nifi.distributed.cache.server.DistributedSetCacheServer -org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/docs/org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer/index.html ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/docs/org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer/index.html b/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/docs/org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer/index.html deleted file mode 100644 index dca3aa1..0000000 --- a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/main/resources/docs/org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer/index.html +++ /dev/null @@ -1,82 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<!-- - 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. ---> -<head> -<meta charset="utf-8" /> -<title>Distributed Map Cache Client Service</title> -<link rel="stylesheet" href="../../css/component-usage.css" type="text/css" /> -</head> - -<body> - <h2>Description:</h2> - - <p>A Controller Service that starts an embedded server and listens for connections from clients. The - server provides the ability to query the cache, add data to the cache, and remove data from the cache.</p> - - - - <p> - <strong>Properties:</strong> - </p> - <p>In the list below, the names of required properties appear - in bold. Any other properties (not in bold) are considered optional. - If a property has a default value, it is indicated. If a property - supports the use of the NiFi Expression Language (or simply, - "expression language"), that is also indicated.</p> - - <ul> - <li><strong>Port</strong> - <ul> - <li>The port to listen on for incoming connections</li> - <li>Default value: 4557</li> - <li>Supports expression language: false</li> - </ul></li> - <li>SSL Context Service - <ul> - <li>If specified, this service will be used to create an SSL Context that will be used to secure communications; if not specified, communications will not be secure</li> - <li>Default value: no default</li> - <li>Supports expression language: false</li> - </ul></li> - <li><strong>Maximum Cache Entries</strong> - <ul> - <li>The maximum number of cache entries that the cache can hold - <li>Default value: 10,000</li> - <li>Supports expression language: false</li> - </ul></li> - <li><strong>Eviction Strategy</strong> - <ul> - <li>Determines which strategy should be used to evict values from the cache to make room for new entries. Valid values: - <code>Least Frequently Used</code>, <code>Least Recently Used</code>, and <code>First In, First Out</code> - <li>Default value: Least Frequently Used</li> - <li>Supports expression language: false</li> - </ul></li> - <li>Persistence Directory - <ul> - <li>If specified, the cache will be persisted in the given directory; if not specified, the cache will be in-memory only</li> - <li>Default value: no default (in-memory)</li> - <li>Supports expression language: true - JVM and System Properties Only</li> - </ul></li> - </ul> - - - <i>See Also:</i> - <ul> - <li><a href="../org.apache.nifi.distributed.cache.client.DistributedMapCacheClientService/index.html">Distributed Map Cache Client Service</a></li> - <li><a href="../org.apache.nifi.ssl.StandardSSLContextService/index.html">Standard SSL Context Service</a></li> - </ul> - -</body> -</html> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/TestServerAndClient.java ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/TestServerAndClient.java b/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/TestServerAndClient.java deleted file mode 100644 index b5f3fd6..0000000 --- a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/java/org/apache/nifi/distributed/cache/server/TestServerAndClient.java +++ /dev/null @@ -1,530 +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.distributed.cache.server; - -import org.apache.nifi.distributed.cache.server.DistributedSetCacheServer; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.net.ConnectException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import org.apache.nifi.components.PropertyDescriptor; -import org.apache.nifi.distributed.cache.client.Deserializer; -import org.apache.nifi.distributed.cache.client.DistributedMapCacheClientService; -import org.apache.nifi.distributed.cache.client.DistributedSetCacheClientService; -import org.apache.nifi.distributed.cache.client.Serializer; -import org.apache.nifi.distributed.cache.client.exception.DeserializationException; -import org.apache.nifi.distributed.cache.server.map.DistributedMapCacheServer; -import org.apache.nifi.reporting.InitializationException; -import org.apache.nifi.ssl.SSLContextService.ClientAuth; -import org.apache.nifi.ssl.StandardSSLContextService; -import org.apache.nifi.util.MockConfigurationContext; -import org.apache.nifi.util.MockControllerServiceInitializationContext; - -import org.apache.commons.lang3.SerializationException; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TestServerAndClient { - - private static Logger LOGGER; - - static { - System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info"); - System.setProperty("org.slf4j.simpleLogger.showDateTime", "true"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.distributed.cache.server.AbstractCacheServer", "debug"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.distributed.cache.client.DistributedMapCacheClientService", "debug"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.distributed.cache.server.TestServerAndClient", "debug"); - System.setProperty("org.slf4j.simpleLogger.log.nifi.remote.io.socket.ssl.SSLSocketChannel", "trace"); - LOGGER = LoggerFactory.getLogger(TestServerAndClient.class); - } - - @Test - public void testNonPersistentSetServerAndClient() throws InitializationException, IOException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create server - final DistributedSetCacheServer server = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - final DistributedSetCacheClientService client = createClient(); - final Serializer<String> serializer = new StringSerializer(); - final boolean added = client.addIfAbsent("test", serializer); - assertTrue(added); - - final boolean contains = client.contains("test", serializer); - assertTrue(contains); - - final boolean addedAgain = client.addIfAbsent("test", serializer); - assertFalse(addedAgain); - - final boolean removed = client.remove("test", serializer); - assertTrue(removed); - - final boolean containedAfterRemove = client.contains("test", serializer); - assertFalse(containedAfterRemove); - - server.shutdownServer(); - } - - @Test - public void testPersistentSetServerAndClient() throws InitializationException, IOException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create server - final DistributedSetCacheServer server = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final File dataFile = new File("target/cache-data"); - deleteRecursively(dataFile); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - serverProperties.put(DistributedSetCacheServer.PERSISTENCE_PATH, dataFile.getAbsolutePath()); - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - final DistributedSetCacheClientService client = createClient(); - final Serializer<String> serializer = new StringSerializer(); - final boolean added = client.addIfAbsent("test", serializer); - final boolean added2 = client.addIfAbsent("test2", serializer); - assertTrue(added); - assertTrue(added2); - - final boolean contains = client.contains("test", serializer); - final boolean contains2 = client.contains("test2", serializer); - assertTrue(contains); - assertTrue(contains2); - - final boolean addedAgain = client.addIfAbsent("test", serializer); - assertFalse(addedAgain); - - final boolean removed = client.remove("test", serializer); - assertTrue(removed); - - final boolean containedAfterRemove = client.contains("test", serializer); - assertFalse(containedAfterRemove); - - server.shutdownServer(); - - final DistributedSetCacheServer newServer = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext newServerInitContext = new MockControllerServiceInitializationContext(newServer, "server2"); - newServer.initialize(newServerInitContext); - - final MockConfigurationContext newServerContext = new MockConfigurationContext(serverProperties, - newServerInitContext.getControllerServiceLookup()); - newServer.startServer(newServerContext); - - assertFalse(client.contains("test", serializer)); - assertTrue(client.contains("test2", serializer)); - - newServer.shutdownServer(); - } - - @Test - public void testPersistentSetServerAndClientWithLFUEvictions() throws InitializationException, IOException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create server - final DistributedSetCacheServer server = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final File dataFile = new File("target/cache-data"); - deleteRecursively(dataFile); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - serverProperties.put(DistributedSetCacheServer.PERSISTENCE_PATH, dataFile.getAbsolutePath()); - serverProperties.put(DistributedSetCacheServer.MAX_CACHE_ENTRIES, "3"); - - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - final DistributedSetCacheClientService client = createClient(); - final Serializer<String> serializer = new StringSerializer(); - final boolean added = client.addIfAbsent("test", serializer); - waitABit(); - final boolean added2 = client.addIfAbsent("test2", serializer); - waitABit(); - final boolean added3 = client.addIfAbsent("test3", serializer); - waitABit(); - assertTrue(added); - assertTrue(added2); - assertTrue(added3); - - final boolean contains = client.contains("test", serializer); - final boolean contains2 = client.contains("test2", serializer); - assertTrue(contains); - assertTrue(contains2); - - final boolean addedAgain = client.addIfAbsent("test", serializer); - assertFalse(addedAgain); - - final boolean added4 = client.addIfAbsent("test4", serializer); - assertTrue(added4); - - // ensure that added3 was evicted because it was used least frequently - assertFalse(client.contains("test3", serializer)); - - server.shutdownServer(); - - final DistributedSetCacheServer newServer = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext newServerInitContext = new MockControllerServiceInitializationContext(newServer, "server2"); - newServer.initialize(newServerInitContext); - - final MockConfigurationContext newServerContext = new MockConfigurationContext(serverProperties, - newServerInitContext.getControllerServiceLookup()); - newServer.startServer(newServerContext); - - assertTrue(client.contains("test", serializer)); - assertTrue(client.contains("test2", serializer)); - assertFalse(client.contains("test3", serializer)); - assertTrue(client.contains("test4", serializer)); - - newServer.shutdownServer(); - } - - @Test - public void testPersistentSetServerAndClientWithFIFOEvictions() throws InitializationException, IOException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create server - final DistributedSetCacheServer server = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final File dataFile = new File("target/cache-data"); - deleteRecursively(dataFile); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - serverProperties.put(DistributedSetCacheServer.PERSISTENCE_PATH, dataFile.getAbsolutePath()); - serverProperties.put(DistributedSetCacheServer.MAX_CACHE_ENTRIES, "3"); - serverProperties.put(DistributedSetCacheServer.EVICTION_POLICY, DistributedSetCacheServer.EVICTION_STRATEGY_FIFO); - - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - final DistributedSetCacheClientService client = createClient(); - final Serializer<String> serializer = new StringSerializer(); - - // add 3 entries to the cache. But, if we add too fast, we'll have the same millisecond - // for the entry time so we don't know which entry will be evicted. So we wait a few millis in between - final boolean added = client.addIfAbsent("test", serializer); - waitABit(); - final boolean added2 = client.addIfAbsent("test2", serializer); - waitABit(); - final boolean added3 = client.addIfAbsent("test3", serializer); - waitABit(); - - assertTrue(added); - assertTrue(added2); - assertTrue(added3); - - final boolean contains = client.contains("test", serializer); - final boolean contains2 = client.contains("test2", serializer); - assertTrue(contains); - assertTrue(contains2); - - final boolean addedAgain = client.addIfAbsent("test", serializer); - assertFalse(addedAgain); - - final boolean added4 = client.addIfAbsent("test4", serializer); - assertTrue(added4); - - // ensure that added3 was evicted because it was used least frequently - assertFalse(client.contains("test", serializer)); - assertTrue(client.contains("test3", serializer)); - - server.shutdownServer(); - - final DistributedSetCacheServer newServer = new DistributedSetCacheServer(); - MockControllerServiceInitializationContext newServerInitContext = new MockControllerServiceInitializationContext(newServer, "server2"); - newServer.initialize(newServerInitContext); - - final MockConfigurationContext newServerContext = new MockConfigurationContext(serverProperties, - newServerInitContext.getControllerServiceLookup()); - newServer.startServer(newServerContext); - - assertFalse(client.contains("test", serializer)); - assertTrue(client.contains("test2", serializer)); - assertTrue(client.contains("test3", serializer)); - assertTrue(client.contains("test4", serializer)); - - newServer.shutdownServer(); - } - - @Test - public void testNonPersistentMapServerAndClient() throws InitializationException, IOException, InterruptedException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create server - final DistributedMapCacheServer server = new DistributedMapCacheServer(); - MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - DistributedMapCacheClientService client = new DistributedMapCacheClientService(); - MockControllerServiceInitializationContext clientInitContext = new MockControllerServiceInitializationContext(client, "client"); - client.initialize(clientInitContext); - - final Map<PropertyDescriptor, String> clientProperties = new HashMap<>(); - clientProperties.put(DistributedMapCacheClientService.HOSTNAME, "localhost"); - clientProperties.put(DistributedMapCacheClientService.COMMUNICATIONS_TIMEOUT, "360 secs"); - MockConfigurationContext clientContext = new MockConfigurationContext(clientProperties, clientInitContext.getControllerServiceLookup()); - client.cacheConfig(clientContext); - final Serializer<String> valueSerializer = new StringSerializer(); - final Serializer<String> keySerializer = new StringSerializer(); - final Deserializer<String> deserializer = new StringDeserializer(); - - final String original = client.getAndPutIfAbsent("testKey", "test", keySerializer, valueSerializer, deserializer); - assertEquals(null, original); - LOGGER.debug("end getAndPutIfAbsent"); - - final boolean contains = client.containsKey("testKey", keySerializer); - assertTrue(contains); - LOGGER.debug("end containsKey"); - - final boolean added = client.putIfAbsent("testKey", "test", keySerializer, valueSerializer); - assertFalse(added); - LOGGER.debug("end putIfAbsent"); - - final String originalAfterPut = client.getAndPutIfAbsent("testKey", "test", keySerializer, valueSerializer, deserializer); - assertEquals("test", originalAfterPut); - LOGGER.debug("end getAndPutIfAbsent"); - - final boolean removed = client.remove("testKey", keySerializer); - assertTrue(removed); - LOGGER.debug("end remove"); - - final boolean containedAfterRemove = client.containsKey("testKey", keySerializer); - assertFalse(containedAfterRemove); - - client.putIfAbsent("testKey", "test", keySerializer, valueSerializer); - client.close(); - try { - client.containsKey("testKey", keySerializer); - fail("Should be closed and not accessible"); - } catch (Exception e) { - - } - client = null; - clientInitContext = null; - clientContext = null; - - DistributedMapCacheClientService client2 = new DistributedMapCacheClientService(); - - MockControllerServiceInitializationContext clientInitContext2 = new MockControllerServiceInitializationContext(client2, "client2"); - client2.initialize(clientInitContext2); - - MockConfigurationContext clientContext2 = new MockConfigurationContext(clientProperties, - clientInitContext2.getControllerServiceLookup()); - client2.cacheConfig(clientContext2); - assertFalse(client2.putIfAbsent("testKey", "test", keySerializer, valueSerializer)); - assertTrue(client2.containsKey("testKey", keySerializer)); - server.shutdownServer(); - Thread.sleep(1000); - try { - client2.containsKey("testKey", keySerializer); - fail("Should have blown exception!"); - } catch (ConnectException e) { - client2 = null; - clientContext2 = null; - clientInitContext2 = null; - } - Thread.sleep(2000); - System.gc(); - LOGGER.debug("end testNonPersistentMapServerAndClient"); - } - - @Test - public void testClientTermination() throws InitializationException, IOException, InterruptedException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create server - final DistributedMapCacheServer server = new DistributedMapCacheServer(); - MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - DistributedMapCacheClientService client = new DistributedMapCacheClientService(); - MockControllerServiceInitializationContext clientInitContext = new MockControllerServiceInitializationContext(client, "client"); - client.initialize(clientInitContext); - - final Map<PropertyDescriptor, String> clientProperties = new HashMap<>(); - clientProperties.put(DistributedMapCacheClientService.HOSTNAME, "localhost"); - clientProperties.put(DistributedMapCacheClientService.COMMUNICATIONS_TIMEOUT, "360 secs"); - MockConfigurationContext clientContext = new MockConfigurationContext(clientProperties, clientInitContext.getControllerServiceLookup()); - client.cacheConfig(clientContext); - final Serializer<String> valueSerializer = new StringSerializer(); - final Serializer<String> keySerializer = new StringSerializer(); - final Deserializer<String> deserializer = new StringDeserializer(); - - final String original = client.getAndPutIfAbsent("testKey", "test", keySerializer, valueSerializer, deserializer); - assertEquals(null, original); - - final boolean contains = client.containsKey("testKey", keySerializer); - assertTrue(contains); - - final boolean added = client.putIfAbsent("testKey", "test", keySerializer, valueSerializer); - assertFalse(added); - - final String originalAfterPut = client.getAndPutIfAbsent("testKey", "test", keySerializer, valueSerializer, deserializer); - assertEquals("test", originalAfterPut); - - final boolean removed = client.remove("testKey", keySerializer); - assertTrue(removed); - - final boolean containedAfterRemove = client.containsKey("testKey", keySerializer); - assertFalse(containedAfterRemove); - - client = null; - clientInitContext = null; - clientContext = null; - Thread.sleep(2000); - System.gc(); - server.shutdownServer(); - } - - @Ignore - @Test - public void testSSLWith2RequestsWithServerTimeout() throws InitializationException, IOException, InterruptedException { - LOGGER.info("Testing " + Thread.currentThread().getStackTrace()[1].getMethodName()); - // Create SSLContext Service - final StandardSSLContextService sslService = new StandardSSLContextService(); - final MockControllerServiceInitializationContext sslServerInitContext = new MockControllerServiceInitializationContext(sslService, - "ssl-context"); - sslService.initialize(sslServerInitContext); - - final Map<PropertyDescriptor, String> sslServerProps = new HashMap<>(); - sslServerProps.put(StandardSSLContextService.KEYSTORE, "src/test/resources/localhost-ks.jks"); - sslServerProps.put(StandardSSLContextService.KEYSTORE_PASSWORD, "localtest"); - sslServerProps.put(StandardSSLContextService.KEYSTORE_TYPE, "JKS"); - sslServerProps.put(StandardSSLContextService.TRUSTSTORE, "src/test/resources/localhost-ts.jks"); - sslServerProps.put(StandardSSLContextService.TRUSTSTORE_PASSWORD, "localtest"); - sslServerProps.put(StandardSSLContextService.TRUSTSTORE_TYPE, "JKS"); - MockConfigurationContext sslServerContext = new MockConfigurationContext(sslServerProps, sslServerInitContext); - sslService.onConfigured(sslServerContext); - sslService.createSSLContext(ClientAuth.REQUIRED); - // Create server - final DistributedMapCacheServer server = new DistributedMapCacheServer(); - final MockControllerServiceInitializationContext serverInitContext = new MockControllerServiceInitializationContext(server, "server"); - server.initialize(serverInitContext); - - final Map<PropertyDescriptor, String> serverProperties = new HashMap<>(); - serverProperties.put(DistributedMapCacheServer.SSL_CONTEXT_SERVICE, "ssl-context"); - final MockConfigurationContext serverContext = new MockConfigurationContext(serverProperties, serverInitContext.getControllerServiceLookup()); - server.startServer(serverContext); - - DistributedMapCacheClientService client = new DistributedMapCacheClientService(); - MockControllerServiceInitializationContext clientInitContext = new MockControllerServiceInitializationContext(client, "client"); - client.initialize(clientInitContext); - - final Map<PropertyDescriptor, String> clientProperties = new HashMap<>(); - clientProperties.put(DistributedMapCacheClientService.HOSTNAME, "localhost"); - clientProperties.put(DistributedMapCacheClientService.COMMUNICATIONS_TIMEOUT, "360 secs"); - clientProperties.put(DistributedMapCacheClientService.SSL_CONTEXT_SERVICE, "ssl-context"); - MockConfigurationContext clientContext = new MockConfigurationContext(clientProperties, clientInitContext.getControllerServiceLookup()); - client.cacheConfig(clientContext); - final Serializer<String> valueSerializer = new StringSerializer(); - final Serializer<String> keySerializer = new StringSerializer(); - final Deserializer<String> deserializer = new StringDeserializer(); - - final String original = client.getAndPutIfAbsent("testKey", "test", keySerializer, valueSerializer, deserializer); - assertEquals(null, original); - - Thread.sleep(30000); - try { - final boolean contains = client.containsKey("testKey", keySerializer); - assertTrue(contains); - } catch (IOException e) { - // this is due to the server timing out in the middle of this request - assertTrue(e.getMessage().contains("Channel is closed")); - } - - server.shutdownServer(); - } - - private void waitABit() { - try { - Thread.sleep(10L); - } catch (final InterruptedException e) { - } - } - - private DistributedSetCacheClientService createClient() throws InitializationException { - final DistributedSetCacheClientService client = new DistributedSetCacheClientService(); - MockControllerServiceInitializationContext clientInitContext = new MockControllerServiceInitializationContext(client, "client"); - client.initialize(clientInitContext); - - final Map<PropertyDescriptor, String> clientProperties = new HashMap<>(); - clientProperties.put(DistributedSetCacheClientService.HOSTNAME, "localhost"); - final MockConfigurationContext clientContext = new MockConfigurationContext(clientProperties, clientInitContext.getControllerServiceLookup()); - client.onConfigured(clientContext); - - return client; - } - - private static class StringSerializer implements Serializer<String> { - @Override - public void serialize(final String value, final OutputStream output) throws SerializationException, IOException { - output.write(value.getBytes(StandardCharsets.UTF_8)); - } - } - - private static class StringDeserializer implements Deserializer<String> { - @Override - public String deserialize(final byte[] input) throws DeserializationException, IOException { - return (input.length == 0) ? null : new String(input, StandardCharsets.UTF_8); - } - } - - private static void deleteRecursively(final File dataFile) throws IOException { - if (dataFile == null || !dataFile.exists()) { - return; - } - - final File[] children = dataFile.listFiles(); - for (final File child : children) { - if (child.isDirectory()) { - deleteRecursively(child); - } else { - for (int i = 0; i < 100 && child.exists(); i++) { - child.delete(); - } - - if (child.exists()) { - throw new IOException("Could not delete " + dataFile.getAbsolutePath()); - } - } - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ks.jks ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ks.jks b/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ks.jks deleted file mode 100755 index 81be31d..0000000 Binary files a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ks.jks and /dev/null differ http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ts.jks ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ts.jks b/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ts.jks deleted file mode 100755 index 820e1e1..0000000 Binary files a/nar-bundles/distributed-cache-services-bundle/distributed-cache-server/src/test/resources/localhost-ts.jks and /dev/null differ http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/distributed-cache-services-nar/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/distributed-cache-services-nar/pom.xml b/nar-bundles/distributed-cache-services-bundle/distributed-cache-services-nar/pom.xml deleted file mode 100644 index 75cab34..0000000 --- a/nar-bundles/distributed-cache-services-bundle/distributed-cache-services-nar/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> -<!-- - 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. ---> - <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.apache.nifi</groupId> - <artifactId>distributed-cache-services-bundle</artifactId> - <version>0.0.1-SNAPSHOT</version> - </parent> - - <artifactId>distributed-cache-services-nar</artifactId> - <name>Distributed Cache Services NAR</name> - <packaging>nar</packaging> - - <dependencies> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>standard-services-api-nar</artifactId> - <type>nar</type> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>distributed-cache-client-service</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>distributed-cache-protocol</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>distributed-cache-server</artifactId> - <version>${project.version}</version> - </dependency> - </dependencies> -</project> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/distributed-cache-services-bundle/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/distributed-cache-services-bundle/pom.xml b/nar-bundles/distributed-cache-services-bundle/pom.xml deleted file mode 100644 index dcfa541..0000000 --- a/nar-bundles/distributed-cache-services-bundle/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <!-- - 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. - --> - <modelVersion>4.0.0</modelVersion> - - <parent> - <groupId>org.apache.nifi</groupId> - <artifactId>standard-services-api-bundle</artifactId> - <version>0.0.1-SNAPSHOT</version> - </parent> - - <artifactId>distributed-cache-services-bundle</artifactId> - <version>0.0.1-SNAPSHOT</version> - <name>Distributed Cache Services Bundle</name> - <packaging>pom</packaging> - - <modules> - <module>distributed-cache-protocol</module> - <module>distributed-cache-client-service</module> - <module>distributed-cache-server</module> - <module>distributed-cache-services-nar</module> - </modules> - - - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>distributed-cache-client-service-api</artifactId> - <version>${standard.services.api.version}</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>ssl-context-service-api</artifactId> - <version>${standard.services.api.version}</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>nifi-processor-utils</artifactId> - <version>0.0.1-SNAPSHOT</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>nifi-stream-utils</artifactId> - <version>0.0.1-SNAPSHOT</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>remote-communications-utils</artifactId> - <version>0.0.1-SNAPSHOT</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>nifi-mock</artifactId> - <version>0.0.1-SNAPSHOT</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>wali</groupId> - <artifactId>wali</artifactId> - <version>3.0.0-SNAPSHOT</version> - </dependency> - <dependency> - <groupId>org.apache.nifi</groupId> - <artifactId>ssl-context-service</artifactId> - <version>0.0.1-SNAPSHOT</version> - <scope>test</scope> - </dependency> - </dependencies> - </dependencyManagement> -</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/cluster/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/cluster/pom.xml b/nar-bundles/framework-bundle/framework/cluster/pom.xml index ad5dda7..78f4527 100644 --- a/nar-bundles/framework-bundle/framework/cluster/pom.xml +++ b/nar-bundles/framework-bundle/framework/cluster/pom.xml @@ -80,7 +80,6 @@ <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> - <version>1.9</version> </dependency> <!-- third party dependencies --> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/core/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/core/pom.xml b/nar-bundles/framework-bundle/framework/core/pom.xml index 547c75d..1836d32 100644 --- a/nar-bundles/framework-bundle/framework/core/pom.xml +++ b/nar-bundles/framework-bundle/framework/core/pom.xml @@ -117,9 +117,8 @@ <artifactId>data-provenance-utils</artifactId> </dependency> <dependency> - <groupId>wali</groupId> + <groupId>org.apache.nifi</groupId> <artifactId>wali</artifactId> - <version>3.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/file-authorization-provider/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/file-authorization-provider/pom.xml b/nar-bundles/framework-bundle/framework/file-authorization-provider/pom.xml new file mode 100644 index 0000000..cb01488 --- /dev/null +++ b/nar-bundles/framework-bundle/framework/file-authorization-provider/pom.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-framework-parent</artifactId> + <version>0.0.1-SNAPSHOT</version> + </parent> + + <artifactId>file-authorization-provider</artifactId> + <version>0.0.1-SNAPSHOT</version> + <name>Authorization Provider: File</name> + + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + </resource> + <resource> + <directory>src/main/xsd</directory> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>jaxb2-maven-plugin</artifactId> + <executions> + <execution> + <id>xjc</id> + <goals> + <goal>xjc</goal> + </goals> + <configuration> + <packageName>org.apache.nifi.user.generated</packageName> + </configuration> + </execution> + </executions> + <configuration> + <generateDirectory>${project.build.directory}/generated-sources/jaxb</generateDirectory> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-file-utils</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-properties</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/java/org/apache/nifi/authorization/FileAuthorizationProvider.java ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/java/org/apache/nifi/authorization/FileAuthorizationProvider.java b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/java/org/apache/nifi/authorization/FileAuthorizationProvider.java new file mode 100644 index 0000000..0f4a75c --- /dev/null +++ b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/java/org/apache/nifi/authorization/FileAuthorizationProvider.java @@ -0,0 +1,568 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import org.apache.nifi.authorization.annotation.AuthorityProviderContext; +import org.apache.nifi.authorization.exception.AuthorityAccessException; +import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authorization.exception.UnknownIdentityException; +import org.apache.nifi.file.FileUtils; +import org.apache.nifi.user.generated.ObjectFactory; +import org.apache.nifi.user.generated.Role; +import org.apache.nifi.user.generated.User; +import org.apache.nifi.user.generated.Users; +import org.apache.nifi.util.NiFiProperties; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +/** + * Provides identity checks and grants authorities. + */ +public class FileAuthorizationProvider implements AuthorityProvider { + + private static final Logger logger = LoggerFactory.getLogger(FileAuthorizationProvider.class); + private static final String USERS_XSD = "/users.xsd"; + private static final String JAXB_GENERATED_PATH = "org.apache.nifi.user.generated"; + private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); + + /** + * Load the JAXBContext. + */ + private static JAXBContext initializeJaxbContext() { + try { + return JAXBContext.newInstance(JAXB_GENERATED_PATH, FileAuthorizationProvider.class.getClassLoader()); + } catch (JAXBException e) { + throw new RuntimeException("Unable to create JAXBContext."); + } + } + + private NiFiProperties properties; + private File usersFile; + private File restoreUsersFile; + private Users users; + private final Set<String> defaultAuthorities = new HashSet<>(); + + @Override + public void initialize(final AuthorityProviderInitializationContext initializationContext) throws ProviderCreationException { + } + + @Override + public void onConfigured(final AuthorityProviderConfigurationContext configurationContext) throws ProviderCreationException { + try { + final String usersFilePath = configurationContext.getProperty("Authorized Users File"); + if (usersFilePath == null || usersFilePath.trim().isEmpty()) { + throw new ProviderCreationException("The authorized users file must be specified."); + } + + // the users file instance will never be null because a default is used + usersFile = new File(usersFilePath); + final File usersFileDirectory = usersFile.getParentFile(); + + // the restore directory is optional and may be null + final File restoreDirectory = properties.getRestoreDirectory(); + + if (restoreDirectory != null) { + + // sanity check that restore directory is a directory, creating it if necessary + FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory); + + // check that restore directory is not the same as the primary directory + if (usersFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { + throw new ProviderCreationException(String.format("Authorized User's directory '%s' is the same as restore directory '%s' ", + usersFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); + } + + // the restore copy will have same file name, but reside in a different directory + restoreUsersFile = new File(restoreDirectory, usersFile.getName()); + + // sync the primary copy with the restore copy + try { + FileUtils.syncWithRestore(usersFile, restoreUsersFile, logger); + } catch (final IOException | IllegalStateException ioe) { + throw new ProviderCreationException(ioe); + } + + } + + // load the users from the specified file + if (usersFile.exists()) { + // find the schema + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final Schema schema = schemaFactory.newSchema(FileAuthorizationProvider.class.getResource(USERS_XSD)); + + // attempt to unmarshal + final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(schema); + final JAXBElement<Users> element = unmarshaller.unmarshal(new StreamSource(usersFile), Users.class); + users = element.getValue(); + } else { + final ObjectFactory objFactory = new ObjectFactory(); + users = objFactory.createUsers(); + } + + // attempt to load a default roles + final String rawDefaultAuthorities = configurationContext.getProperty("Default User Roles"); + if (StringUtils.isNotBlank(rawDefaultAuthorities)) { + final Set<String> invalidDefaultAuthorities = new HashSet<>(); + + // validate the specified authorities + final String[] rawDefaultAuthorityList = rawDefaultAuthorities.split(","); + for (String rawAuthority : rawDefaultAuthorityList) { + rawAuthority = rawAuthority.trim(); + final Authority authority = Authority.valueOfAuthority(rawAuthority); + if (authority == null) { + invalidDefaultAuthorities.add(rawAuthority); + } else { + defaultAuthorities.add(rawAuthority); + } + } + + // report any unrecognized authorities + if (!invalidDefaultAuthorities.isEmpty()) { + logger.warn(String.format("The following default role(s) '%s' were not recognized. Possible values: %s.", + StringUtils.join(invalidDefaultAuthorities, ", "), StringUtils.join(Authority.getRawAuthorities(), ", "))); + } + } + } catch (IOException | ProviderCreationException | SAXException | JAXBException e) { + throw new ProviderCreationException(e); + } + + } + + @Override + public void preDestruction() { + } + + /** + * Determines if this provider has a default role. + * + * @return + */ + private boolean hasDefaultRoles() { + return !defaultAuthorities.isEmpty(); + } + + /** + * Determines if the specified dn is known to this authority provider. When + * this provider is configured to have default role(s), all dn are + * considered to exist. + * + * @param dn + * @return True if he dn is known, false otherwise + */ + @Override + public boolean doesDnExist(String dn) throws AuthorityAccessException { + if (hasDefaultRoles()) { + return true; + } + + final User user = getUser(dn); + return user != null; + } + + /** + * Loads the authorities for the specified user. If this provider is + * configured for default user role(s) and a non existent dn is specified, a + * new user will be automatically created with the default role(s). + * + * @param dn + * @return + * @throws UnknownIdentityException + * @throws AuthorityAccessException + */ + @Override + public synchronized Set<Authority> getAuthorities(String dn) throws UnknownIdentityException, AuthorityAccessException { + final Set<Authority> authorities = EnumSet.noneOf(Authority.class); + + // get the user + final User user = getUser(dn); + + // ensure the user was located + if (user == null) { + if (hasDefaultRoles()) { + logger.debug(String.format("User DN not found: %s. Creating new user with default roles.", dn)); + + // create the user (which will automatically add any default authorities) + addUser(dn, null); + + // get the authorities for the newly created user + authorities.addAll(getAuthorities(dn)); + } else { + throw new UnknownIdentityException(String.format("User DN not found: %s.", dn)); + } + } else { + // create the authorities that this user has + for (final Role role : user.getRole()) { + authorities.add(Authority.valueOfAuthority(role.getName())); + } + } + + return authorities; + } + + /** + * Adds the specified authorities to the specified user. Regardless of + * whether this provider is configured for a default user role, when a non + * existent dn is specified, an UnknownIdentityException will be thrown. + * + * @param dn + * @param authorities + * @throws UnknownIdentityException + * @throws AuthorityAccessException + */ + @Override + public synchronized void setAuthorities(String dn, Set<Authority> authorities) throws UnknownIdentityException, AuthorityAccessException { + // get the user + final User user = getUser(dn); + + // ensure the user was located + if (user == null) { + throw new UnknownIdentityException(String.format("User DN not found: %s.", dn)); + } + + // add the user authorities + setUserAuthorities(user, authorities); + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + /** + * Adds the specified authorities to the specified user. + * + * @param user + * @param authorities + */ + private void setUserAuthorities(final User user, final Set<Authority> authorities) { + // clear the existing rules + user.getRole().clear(); + + // set the new roles + final ObjectFactory objFactory = new ObjectFactory(); + for (final Authority authority : authorities) { + final Role role = objFactory.createRole(); + role.setName(authority.toString()); + + // add the new role + user.getRole().add(role); + } + } + + /** + * Adds the specified user. If this provider is configured with default + * role(s) they will be added to the new user. + * + * @param dn + * @param group + * @throws UnknownIdentityException + * @throws AuthorityAccessException + */ + @Override + public synchronized void addUser(String dn, String group) throws IdentityAlreadyExistsException, AuthorityAccessException { + final User user = getUser(dn); + + // ensure the user doesn't already exist + if (user != null) { + throw new IdentityAlreadyExistsException(String.format("User DN already exists: %s", dn)); + } + + // create the new user + final ObjectFactory objFactory = new ObjectFactory(); + final User newUser = objFactory.createUser(); + + // set the user properties + newUser.setDn(dn); + newUser.setGroup(group); + + // add default roles if appropriate + if (hasDefaultRoles()) { + for (final String authority : defaultAuthorities) { + Role role = objFactory.createRole(); + role.setName(authority); + + // add the role + newUser.getRole().add(role); + } + } + + // add the user + users.getUser().add(newUser); + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + /** + * Gets the users for the specified authority. + * + * @param authority + * @return + * @throws AuthorityAccessException + */ + @Override + public synchronized Set<String> getUsers(Authority authority) throws AuthorityAccessException { + final Set<String> userSet = new HashSet<>(); + for (final User user : users.getUser()) { + for (final Role role : user.getRole()) { + if (role.getName().equals(authority.toString())) { + userSet.add(user.getDn()); + } + } + } + return userSet; + } + + /** + * Removes the specified user. Regardless of whether this provider is + * configured for a default user role, when a non existent dn is specified, + * an UnknownIdentityException will be thrown. + * + * @param dn + * @throws UnknownIdentityException + * @throws AuthorityAccessException + */ + @Override + public synchronized void revokeUser(String dn) throws UnknownIdentityException, AuthorityAccessException { + // get the user + final User user = getUser(dn); + + // ensure the user was located + if (user == null) { + throw new UnknownIdentityException(String.format("User DN not found: %s.", dn)); + } + + // remove the specified user + users.getUser().remove(user); + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + @Override + public void setUsersGroup(Set<String> dns, String group) throws UnknownIdentityException, AuthorityAccessException { + final Collection<User> groupedUsers = new HashSet<>(); + + // get the specified users + for (final String dn : dns) { + // get the user + final User user = getUser(dn); + + // ensure the user was located + if (user == null) { + throw new UnknownIdentityException(String.format("User DN not found: %s.", dn)); + } + + groupedUsers.add(user); + } + + // update each user group + for (final User user : groupedUsers) { + user.setGroup(group); + } + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + @Override + public void ungroupUser(String dn) throws UnknownIdentityException, AuthorityAccessException { + // get the user + final User user = getUser(dn); + + // ensure the user was located + if (user == null) { + throw new UnknownIdentityException(String.format("User DN not found: %s.", dn)); + } + + // remove the users group + user.setGroup(null); + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + @Override + public void ungroup(String group) throws AuthorityAccessException { + // get the user group + final Collection<User> userGroup = getUserGroup(group); + + // ensure the user group was located + if (userGroup == null) { + return; + } + + // update each user group + for (final User user : userGroup) { + user.setGroup(null); + } + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + @Override + public String getGroupForUser(String dn) throws UnknownIdentityException, AuthorityAccessException { + // get the user + final User user = getUser(dn); + + // ensure the user was located + if (user == null) { + throw new UnknownIdentityException(String.format("User DN not found: %s.", dn)); + } + + return user.getGroup(); + } + + @Override + public void revokeGroup(String group) throws UnknownIdentityException, AuthorityAccessException { + // get the user group + final Collection<User> userGroup = getUserGroup(group); + + // ensure the user group was located + if (userGroup == null) { + throw new UnknownIdentityException(String.format("User group not found: %s.", group)); + } + + // remove each user in the group + for (final User user : userGroup) { + users.getUser().remove(user); + } + + try { + // save the file + save(); + } catch (Exception e) { + throw new AuthorityAccessException(e.getMessage(), e); + } + } + + /** + * Locates the user with the specified DN. + * + * @param dn + * @return + */ + private User getUser(String dn) throws UnknownIdentityException { + // ensure the DN was specified + if (dn == null) { + throw new UnknownIdentityException("User DN not specified."); + } + + // attempt to get the user and ensure it was located + User desiredUser = null; + for (final User user : users.getUser()) { + if (dn.equalsIgnoreCase(user.getDn())) { + desiredUser = user; + break; + } + } + + return desiredUser; + } + + /** + * Locates all users that are part of the specified group. + * + * @param group + * @return + * @throws UnknownIdentityException + */ + private Collection<User> getUserGroup(String group) throws UnknownIdentityException { + // ensure the DN was specified + if (group == null) { + throw new UnknownIdentityException("User group not specified."); + } + + // get all users with this group + Collection<User> userGroup = null; + for (final User user : users.getUser()) { + if (group.equals(user.getGroup())) { + if (userGroup == null) { + userGroup = new HashSet<>(); + } + userGroup.add(user); + } + } + + return userGroup; + } + + /** + * Saves the users file. + * + * @throws Exception + */ + private void save() throws Exception { + final Marshaller marshaller = JAXB_CONTEXT.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + // save users to restore directory before primary directory + if (restoreUsersFile != null) { + marshaller.marshal(users, restoreUsersFile); + } + + // save users to primary directory + marshaller.marshal(users, usersFile); + } + + @AuthorityProviderContext + public void setNiFiProperties(NiFiProperties properties) { + this.properties = properties; + } +} http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/resources/META-INF/services/org.apache.nifi.authorization.AuthorityProvider ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/resources/META-INF/services/org.apache.nifi.authorization.AuthorityProvider b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/resources/META-INF/services/org.apache.nifi.authorization.AuthorityProvider new file mode 100755 index 0000000..93d2941 --- /dev/null +++ b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/resources/META-INF/services/org.apache.nifi.authorization.AuthorityProvider @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.authorization.FileAuthorizationProvider http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/xsd/users.xsd ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/xsd/users.xsd b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/xsd/users.xsd new file mode 100644 index 0000000..4ee1e17 --- /dev/null +++ b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/main/xsd/users.xsd @@ -0,0 +1,64 @@ +<?xml version="1.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. +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <!-- role --> + <xs:complexType name="Role"> + <xs:attribute name="name"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:enumeration value="ROLE_MONITOR"/> + <xs:enumeration value="ROLE_PROVENANCE"/> + <xs:enumeration value="ROLE_DFM"/> + <xs:enumeration value="ROLE_ADMIN"/> + <xs:enumeration value="ROLE_PROXY"/> + <xs:enumeration value="ROLE_NIFI"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> + + <!-- user --> + <xs:complexType name="User"> + <xs:sequence> + <xs:element name="role" type="Role" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="dn"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:pattern value=".*[^\s].*"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="group"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:pattern value=".*[^\s].*"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> + + <!-- users --> + <xs:element name="users"> + <xs:complexType> + <xs:sequence> + <xs:element name="user" type="User" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> +</xs:schema> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/file-authorization-provider/src/test/java/org/apache/nifi/authorization/FileAuthorizationProviderTest.java ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/file-authorization-provider/src/test/java/org/apache/nifi/authorization/FileAuthorizationProviderTest.java b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/test/java/org/apache/nifi/authorization/FileAuthorizationProviderTest.java new file mode 100644 index 0000000..3d0196d --- /dev/null +++ b/nar-bundles/framework-bundle/framework/file-authorization-provider/src/test/java/org/apache/nifi/authorization/FileAuthorizationProviderTest.java @@ -0,0 +1,127 @@ +/* + * 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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.file.FileUtils; +import org.apache.nifi.util.NiFiProperties; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; +import org.junit.Ignore; +import org.mockito.Mockito; + +@Ignore +public class FileAuthorizationProviderTest { + + private FileAuthorizationProvider provider; + + private File primary; + + private File restore; + + private NiFiProperties mockProperties; + + private AuthorityProviderConfigurationContext mockConfigurationContext; + + @Before + public void setup() throws IOException { + + primary = new File("target/primary/users.txt"); + restore = new File("target/restore/users.txt"); + + System.out.println("absolute path: " + primary.getAbsolutePath()); + + mockProperties = mock(NiFiProperties.class); + when(mockProperties.getRestoreDirectory()).thenReturn(restore.getParentFile()); + + mockConfigurationContext = mock(AuthorityProviderConfigurationContext.class); + when(mockConfigurationContext.getProperty(Mockito.eq("Authorized Users File"))).thenReturn(primary.getPath()); + + provider = new FileAuthorizationProvider(); + provider.setNiFiProperties(mockProperties); + provider.initialize(null); + } + + @After + public void cleanup() throws Exception { + deleteFile(primary); + deleteFile(restore); + } + + private boolean deleteFile(final File file) { + if(file.isDirectory()) { + FileUtils.deleteFilesInDir(file, null, null, true, true); + } + return FileUtils.deleteFile(file, null, 10); + } + + @Test + public void testPostContructionWhenRestoreDoesNotExist() throws Exception { + + byte[] primaryBytes = "<users/>".getBytes(); + FileOutputStream fos = new FileOutputStream(primary); + fos.write(primaryBytes); + fos.close(); + + provider.onConfigured(mockConfigurationContext); + assertEquals(primary.length(), restore.length()); + } + + @Test + public void testPostContructionWhenPrimaryDoesNotExist() throws Exception { + + byte[] restoreBytes = "<users/>".getBytes(); + FileOutputStream fos = new FileOutputStream(restore); + fos.write(restoreBytes); + fos.close(); + + provider.onConfigured(mockConfigurationContext); + assertEquals(restore.length(), primary.length()); + + } + + @Test(expected = ProviderCreationException.class) + public void testPostContructionWhenPrimaryDifferentThanRestore() throws Exception { + + byte[] primaryBytes = "<users></users>".getBytes(); + FileOutputStream fos = new FileOutputStream(primary); + fos.write(primaryBytes); + fos.close(); + + byte[] restoreBytes = "<users/>".getBytes(); + fos = new FileOutputStream(restore); + fos.write(restoreBytes); + fos.close(); + + provider.onConfigured(mockConfigurationContext); + } + + @Test + public void testPostContructionWhenPrimaryAndBackupDoNotExist() throws Exception { + + provider.onConfigured(mockConfigurationContext); + assertEquals(0, restore.length()); + assertEquals(restore.length(), primary.length()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/pom.xml b/nar-bundles/framework-bundle/framework/pom.xml index 65fdaf1..f8ccdd0 100644 --- a/nar-bundles/framework-bundle/framework/pom.xml +++ b/nar-bundles/framework-bundle/framework/pom.xml @@ -35,6 +35,7 @@ <module>cluster-protocol</module> <module>cluster-web</module> <module>cluster</module> + <module>file-authorization-provider</module> <module>cluster-authorization-provider</module> <module>user-actions</module> <module>administration</module> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/resources/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/resources/pom.xml b/nar-bundles/framework-bundle/framework/resources/pom.xml index 9c984e7..ea25529 100644 --- a/nar-bundles/framework-bundle/framework/resources/pom.xml +++ b/nar-bundles/framework-bundle/framework/resources/pom.xml @@ -29,6 +29,9 @@ <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <attach>true</attach> + </configuration> <executions> <execution> <id>make shared resource</id> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/runtime/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/runtime/pom.xml b/nar-bundles/framework-bundle/framework/runtime/pom.xml index af4b404..e193729 100644 --- a/nar-bundles/framework-bundle/framework/runtime/pom.xml +++ b/nar-bundles/framework-bundle/framework/runtime/pom.xml @@ -29,6 +29,10 @@ <artifactId>nifi-nar</artifactId> </dependency> <dependency> + <groupId>org.apache.nifi</groupId> + <artifactId>nifi-properties</artifactId> + </dependency> + <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/19d4a150/nar-bundles/framework-bundle/framework/site-to-site/pom.xml ---------------------------------------------------------------------- diff --git a/nar-bundles/framework-bundle/framework/site-to-site/pom.xml b/nar-bundles/framework-bundle/framework/site-to-site/pom.xml index ce18ec7..30cd325 100644 --- a/nar-bundles/framework-bundle/framework/site-to-site/pom.xml +++ b/nar-bundles/framework-bundle/framework/site-to-site/pom.xml @@ -43,6 +43,10 @@ </dependency> <dependency> <groupId>org.apache.nifi</groupId> + <artifactId>nifi-properties</artifactId> + </dependency> + <dependency> + <groupId>org.apache.nifi</groupId> <artifactId>core-api</artifactId> </dependency> <dependency> @@ -68,7 +72,6 @@ <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> - <version>4.3.6</version> </dependency> <dependency> <groupId>org.apache.nifi</groupId> @@ -77,7 +80,6 @@ <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> - <version>1.10.8</version> <scope>test</scope> </dependency> </dependencies>
