NIFI-6729 - Created AbstractSingleAttributeBasedControllerServiceLookup and updated DBCPConnectionPoolLookup and AzureStorageCredentialsControllerServiceLookup to inherit from it.

This closes #3774.

Signed-off-by: Peter Turcsanyi <turcsanyi@apache.org>
This commit is contained in:
Tamas Palfy 2019-10-01 11:25:30 +02:00 committed by Peter Turcsanyi
parent 7f96fa1d0d
commit 84a05c8595
8 changed files with 524 additions and 187 deletions

View File

@ -33,6 +33,11 @@
<artifactId>nifi-utils</artifactId>
<version>1.11.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-service-utils</artifactId>
<version>1.11.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-record-serialization-service-api</artifactId>

View File

@ -16,26 +16,11 @@
*/
package org.apache.nifi.services.azure.storage;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.annotation.behavior.DynamicProperty;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnDisabled;
import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.apache.nifi.service.lookup.AbstractSingleAttributeBasedControllerServiceLookup;
import java.util.Map;
@Tags({ "azure", "microsoft", "cloud", "storage", "blob", "queue", "credentials" })
@ -45,97 +30,26 @@ import java.util.Map;
"This will allow multiple AzureStorageCredentialsServices to be defined and registered, and then selected dynamically at runtime by tagging flow files " +
"with the appropriate 'azure.storage.credentials.name' attribute.")
@DynamicProperty(name = "The name to register AzureStorageCredentialsService", value = "The AzureStorageCredentialsService",
description = "If 'azure.storage.credentials.name' attribute contains the name of the dynamic property, then the AzureStorageCredentialsService (registered in the value) will be selected.",
description = "If '" + AzureStorageCredentialsControllerServiceLookup.AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE + "' attribute contains " +
"the name of the dynamic property, then the AzureStorageCredentialsService (registered in the value) will be selected.",
expressionLanguageScope = ExpressionLanguageScope.NONE)
public class AzureStorageCredentialsControllerServiceLookup extends AbstractControllerService implements AzureStorageCredentialsService {
public class AzureStorageCredentialsControllerServiceLookup
extends AbstractSingleAttributeBasedControllerServiceLookup<AzureStorageCredentialsService> implements AzureStorageCredentialsService {
public static final String AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE = "azure.storage.credentials.name";
private volatile Map<String, AzureStorageCredentialsService> serviceMap;
@Override
protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
return new PropertyDescriptor.Builder()
.name(propertyDescriptorName)
.description("The " + AzureStorageCredentialsService.class.getSimpleName() + " to return when " + AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE + " = '" + propertyDescriptorName + "'")
.identifiesControllerService(AzureStorageCredentialsService.class)
.addValidator(StandardValidators.NON_BLANK_VALIDATOR)
.build();
protected String getLookupAttribute() {
return AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE;
}
@Override
protected Collection<ValidationResult> customValidate(ValidationContext context) {
final List<ValidationResult> results = new ArrayList<>();
int numDefinedServices = 0;
for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
if (descriptor.isDynamic()) {
numDefinedServices++;
}
final String referencedId = context.getProperty(descriptor).getValue();
if (this.getIdentifier().equals(referencedId)) {
results.add(new ValidationResult.Builder()
.subject(descriptor.getDisplayName())
.explanation("the current service cannot be registered as an " + AzureStorageCredentialsService.class.getSimpleName() + " to lookup")
.valid(false)
.build());
}
}
if (numDefinedServices == 0) {
results.add(new ValidationResult.Builder()
.subject(this.getClass().getSimpleName())
.explanation("at least one " + AzureStorageCredentialsService.class.getSimpleName() + " must be defined via dynamic properties")
.valid(false)
.build());
}
return results;
}
@OnEnabled
public void onEnabled(final ConfigurationContext context) {
final Map<String, AzureStorageCredentialsService> map = new HashMap<>();
for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
if (descriptor.isDynamic()) {
final AzureStorageCredentialsService service = context.getProperty(descriptor).asControllerService(AzureStorageCredentialsService.class);
map.put(descriptor.getName(), service);
}
}
serviceMap = Collections.unmodifiableMap(map);
}
@OnDisabled
public void onDisabled() {
serviceMap = null;
public Class<AzureStorageCredentialsService> getServiceType() {
return AzureStorageCredentialsService.class;
}
@Override
public AzureStorageCredentialsDetails getStorageCredentialsDetails(Map<String, String> attributes) throws ProcessException {
final AzureStorageCredentialsService service = lookupAzureStorageCredentialsService(attributes);
return service.getStorageCredentialsDetails(attributes);
}
private AzureStorageCredentialsService lookupAzureStorageCredentialsService(Map<String, String> attributes) {
if (!attributes.containsKey(AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE)) {
throw new ProcessException("Attributes must contain an attribute name '" + AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE + "'");
}
final String storageCredentialService = attributes.get(AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE);
if (StringUtils.isBlank(storageCredentialService)) {
throw new ProcessException(AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE + " cannot be null or blank");
}
final AzureStorageCredentialsService service = serviceMap.get(storageCredentialService);
if (service == null) {
throw new ProcessException("No " + AzureStorageCredentialsService.class.getSimpleName() + " was found for " +
AZURE_STORAGE_CREDENTIALS_NAME_ATTRIBUTE + " '" + storageCredentialService + "'");
}
return service;
public AzureStorageCredentialsDetails getStorageCredentialsDetails(Map<String, String> attributes) {
return lookupService(attributes).getStorageCredentialsDetails(attributes);
}
}

View File

@ -0,0 +1,50 @@
<?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.
-->
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-extension-utils</artifactId>
<version>1.11.0-SNAPSHOT</version>
</parent>
<artifactId>nifi-service-utils</artifactId>
<packaging>jar</packaging>
<description>
This nifi-service-utils module is designed to capture common patterns
and utilities that can be leveraged by other services or components to
help promote reuse. These patterns may become framework level features
or may simply be made available through this utility. It is ok for this
module to have dependencies but care should be taken when adding dependencies
as this increases the cost of utilizing this module in various nars.
</description>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
<version>1.11.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
<version>1.11.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,162 @@
/*
* 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 andf
* limitations under the License.
*/
package org.apache.nifi.service.lookup;
import org.apache.nifi.annotation.lifecycle.OnDisabled;
import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* A lookup ControllerService that can choose one (from probably multiple) ControllerServices of the given type {@link S}.
* <p>
* Selection is based on a single {@linkplain String} lookup key.
* <p>
* Lookup key is provided as a value in an attribute map (usually coming form a flowfile)
* with a predefined key (see {@link #getLookupAttribute()}).
*
* @param <S> The type of service to be looked up
*/
public abstract class AbstractSingleAttributeBasedControllerServiceLookup<S extends ControllerService> extends AbstractControllerService {
protected volatile Map<String, S> serviceMap;
/**
* @return the Class that represents the type of service that will be returned by {@link #lookupService(Map)}
*/
public abstract Class<S> getServiceType();
/**
* @return the name of attribute (usually from a flowfile) the value of which serves as the lookup key
* for the desired service (of type {@link S})
*/
protected abstract String getLookupAttribute();
/**
* Returns a ControllerService (of type {@link S}) based on the provided attributes map (usually by retrieving
* a lookup attribute from it via {@link #getLookupAttribute()} and use it to identify the appropriate service).
* @param attributes Map containing the lookup attribute based on which ControllerService is chosen
* @return the chosen ControllerService
*/
public S lookupService(Map<String, String> attributes) {
if (attributes == null) {
throw new ProcessException("Attributes map is null");
} else if (!attributes.containsKey(getLookupAttribute())) {
throw new ProcessException("Attributes must contain an attribute name '" + getLookupAttribute() + "'");
}
Object lookupKey = Optional.of(getLookupAttribute())
.map(attributes::get)
.orElseThrow(() -> new ProcessException(getLookupAttribute() + " cannot be null or blank"));
S service = serviceMap.get(lookupKey);
if (service == null) {
throw new ProcessException("No " + getServiceName() + " found for " + getLookupAttribute());
}
return service;
}
@OnEnabled
public void onEnabled(final ConfigurationContext context) {
Map<String, S> serviceMap = new HashMap<>();
context.getProperties().keySet().stream()
.filter(PropertyDescriptor::isDynamic)
.forEach(propertyDescriptor -> {
S service = context.getProperty(propertyDescriptor).asControllerService(getServiceType());
serviceMap.put(propertyDescriptor.getName(), service);
});
this.serviceMap = Collections.unmodifiableMap(serviceMap);
}
@OnDisabled
public void onDisabled() {
serviceMap = null;
}
@Override
protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
return lookupKeyPropertyDescriptor(propertyDescriptorName);
}
@Override
protected Collection<ValidationResult> customValidate(ValidationContext context) {
return validateForAtLeastOneService(context);
}
protected PropertyDescriptor lookupKeyPropertyDescriptor(String propertyDescriptorName) {
return new PropertyDescriptor.Builder()
.name(propertyDescriptorName)
.description("The " + getServiceName() + " to return when " + getLookupAttribute() + " = '" + propertyDescriptorName + "'")
.identifiesControllerService(getServiceType())
.addValidator(StandardValidators.NON_BLANK_VALIDATOR)
.build();
}
private Collection<ValidationResult> validateForAtLeastOneService(ValidationContext context) {
final List<ValidationResult> results = new ArrayList<>();
int numDefinedServices = 0;
for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
if (descriptor.isDynamic()) {
numDefinedServices++;
}
final String referencedId = context.getProperty(descriptor).getValue();
if (this.getIdentifier().equals(referencedId)) {
numDefinedServices--;
results.add(new ValidationResult.Builder()
.subject(descriptor.getDisplayName())
.explanation("the current service cannot be registered as a " + getServiceName() + " to lookup")
.valid(false)
.build());
}
}
if (numDefinedServices == 0) {
results.add(new ValidationResult.Builder()
.subject(this.getClass().getSimpleName())
.explanation("at least one " + getServiceName() + " must be defined via dynamic properties")
.valid(false)
.build());
}
return results;
}
protected String getServiceName() {
return getServiceType().getSimpleName();
}
}

View File

@ -0,0 +1,276 @@
/*
* 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 andf
* limitations under the License.
*/
package org.apache.nifi.service.lookup;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.util.MockConfigurationContext;
import org.apache.nifi.util.MockControllerServiceInitializationContext;
import org.apache.nifi.util.MockProcessContext;
import org.apache.nifi.util.MockValidationContext;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TestAbstractSingleAttributeBasedControllerServiceLookup {
private static final String LOOKUP_ATTRIBUTE = "lookupAttribute";
private static final String TEST_SUBJECT_IDENTIFIER = "testSubjectIdentifier";
private static final Class<ControllerService> SERVICE_TYPE = ControllerService.class;
private final AbstractSingleAttributeBasedControllerServiceLookup<ControllerService> testSubject = Mockito.spy(AbstractSingleAttributeBasedControllerServiceLookup.class);
private Map<PropertyDescriptor, String> properties;
@Before
public void setUp() throws Exception {
when(testSubject.getLookupAttribute()).thenReturn(LOOKUP_ATTRIBUTE);
when(testSubject.getServiceType()).thenReturn(SERVICE_TYPE);
when(testSubject.getIdentifier()).thenReturn(TEST_SUBJECT_IDENTIFIER);
properties = new HashMap<>();
}
@Test(expected = Exception.class)
public void testLookupShouldThrowExceptionWhenQueriedServiceMappedInPropertiesButWasntCreated() {
// GIVEN
String mappedCreatedServiceID = "mappedCreatedServiceID";
String mappedNotCreatedServiceID = "mappedNotCreatedServiceID";
ControllerService mappedCreatedService = mock(SERVICE_TYPE);
MockControllerServiceInitializationContext serviceLookup = new MockControllerServiceInitializationContext(mappedCreatedService, mappedCreatedServiceID);
String dynamicProperty1 = "property1";
String dynamicProperty2 = "property2";
mapService(dynamicProperty1, mappedCreatedServiceID);
mapService(dynamicProperty2, mappedNotCreatedServiceID);
// WHEN
testSubject.onEnabled(new MockConfigurationContext(properties, serviceLookup));
}
@Test
public void testLookupShouldThrowExceptionWhenAttributeMapIsNull() {
// GIVEN
String mappedCreatedServiceID = "mappedCreatedServiceID";
ControllerService mappedCreatedService = mock(SERVICE_TYPE);
MockControllerServiceInitializationContext serviceLookup = new MockControllerServiceInitializationContext(mappedCreatedService, mappedCreatedServiceID);
// WHEN
testSubject.onEnabled(new MockConfigurationContext(properties, serviceLookup));
try {
testSubject.lookupService(null);
fail();
} catch (ProcessException e) {
assertEquals("Attributes map is null", e.getMessage());
}
}
@Test
public void testLookupShouldThrowExceptionWhenAttributeMapHasNoLookupAttribute() {
// GIVEN
String mappedCreatedServiceID = "mappedCreatedServiceID";
ControllerService mappedCreatedService = mock(SERVICE_TYPE);
MockControllerServiceInitializationContext serviceLookup = new MockControllerServiceInitializationContext(mappedCreatedService, mappedCreatedServiceID);
// WHEN
testSubject.onEnabled(new MockConfigurationContext(properties, serviceLookup));
try {
testSubject.lookupService(new HashMap<>());
fail();
} catch (ProcessException e) {
assertEquals("Attributes must contain an attribute name '" + LOOKUP_ATTRIBUTE + "'", e.getMessage());
}
}
@Test
public void testLookupShouldThrowExceptionWhenQueriedServiceWasCreatedButWasntMappedInProperties() {
// GIVEN
String mappedCreatedServiceID = "mappedCreatedServiceID";
String notMappedCreatedServiceID = "notMappedCreatedServiceID";
ControllerService mappedCreatedService = mock(SERVICE_TYPE);
ControllerService notMappedCreatedService = mock(SERVICE_TYPE);
MockControllerServiceInitializationContext serviceLookup = new MockControllerServiceInitializationContext(mappedCreatedService, mappedCreatedServiceID);
serviceLookup.addControllerService(notMappedCreatedService, notMappedCreatedServiceID);
String dynamicProperty1 = "property1";
String dynamicProperty2 = "property2";
mapService(dynamicProperty1, mappedCreatedServiceID);
String lookupServiceKey = dynamicProperty2;
// WHEN
testSubject.onEnabled(new MockConfigurationContext(properties, serviceLookup));
try {
testSubject.lookupService(createAttributes(lookupServiceKey));
fail();
} catch (ProcessException e) {
assertEquals("No ControllerService found for lookupAttribute", e.getMessage());
}
}
@Test
public void testLookupShouldReturnQueriedService() {
// GIVEN
String mappedCreatedServiceID1 = "mappedCreatedServiceID1";
String mappedCreatedServiceID2 = "mappedCreatedServiceID2";
ControllerService mappedCreatedService1 = mock(SERVICE_TYPE);
ControllerService mappedCreatedService2 = mock(SERVICE_TYPE);
MockControllerServiceInitializationContext serviceLookup = new MockControllerServiceInitializationContext(mappedCreatedService1, mappedCreatedServiceID1);
serviceLookup.addControllerService(mappedCreatedService2, mappedCreatedServiceID2);
String dynamicProperty1 = "property1";
String dynamicProperty2 = "property2";
mapService(dynamicProperty1, mappedCreatedServiceID1);
mapService(dynamicProperty2, mappedCreatedServiceID2);
String lookupServiceKey = dynamicProperty2;
ControllerService expected = mappedCreatedService2;
// WHEN
testSubject.onEnabled(new MockConfigurationContext(properties, serviceLookup));
ControllerService actual = testSubject.lookupService(createAttributes(lookupServiceKey));
// THEN
assertEquals(expected, actual);
}
@Test
public void testCustomValidateShouldReturnErrorWhenNoServiceIsDefined() {
// GIVEN
ValidationContext context = new MockValidationContext(new MockProcessContext(testSubject));
// WHEN
Collection<ValidationResult> actual = testSubject.customValidate(context);
// THEN
assertThat(
actual.stream().map(ValidationResult::getExplanation).collect(Collectors.toList()),
hasItem(containsString("at least one " + SERVICE_TYPE.getSimpleName() + " must be defined via dynamic properties"))
);
}
@Test
public void testCustomValidateShouldReturnErrorWhenSelfAndOtherServiceIsMapped() {
MockProcessContext processContext = new MockProcessContext(testSubject);
processContext.setProperty("property1", "service1");
processContext.setProperty("property2", TEST_SUBJECT_IDENTIFIER);
ValidationContext context = new MockValidationContext(processContext);
// WHEN
Collection<ValidationResult> actual = testSubject.customValidate(context);
// THEN
assertThat(
actual.stream().map(ValidationResult::getExplanation).collect(Collectors.toList()),
hasItem(containsString("the current service cannot be registered as a " + SERVICE_TYPE.getSimpleName() + " to lookup"))
);
}
@Test
public void testCustomValidateShouldReturnErrorsWhenOnlySelfIsMapped() {
MockProcessContext processContext = new MockProcessContext(testSubject);
processContext.setProperty("property1", TEST_SUBJECT_IDENTIFIER);
ValidationContext context = new MockValidationContext(processContext);
// WHEN
Collection<ValidationResult> actual = testSubject.customValidate(context);
// THEN
assertThat(
actual.stream().map(ValidationResult::getExplanation).collect(Collectors.toList()),
hasItems(
containsString("the current service cannot be registered as a " + SERVICE_TYPE.getSimpleName() + " to lookup"),
containsString("at least one " + SERVICE_TYPE.getSimpleName() + " must be defined via dynamic properties")
)
);
}
@Test
public void testCustomValidateShouldReturnNoErrorWhenAServiceIsDefined() {
MockProcessContext processContext = new MockProcessContext(testSubject);
processContext.setProperty("property1", "service1");
ValidationContext context = new MockValidationContext(processContext);
// WHEN
Collection<ValidationResult> actual = testSubject.customValidate(context);
// THEN
assertEquals(Collections.emptyList(), new ArrayList<>(actual));
}
@Test
public void testGetServiceType() {
Class<ControllerService> actual = testSubject.getServiceType();
assertEquals(SERVICE_TYPE, actual);
}
@Test
public void testLookupAttribute() {
String actual = testSubject.getLookupAttribute();
assertEquals(LOOKUP_ATTRIBUTE, actual);
}
private void mapService(String dynamicProperty, String registeredService) {
properties.put(
new PropertyDescriptor.Builder()
.name(dynamicProperty)
.addValidator(StandardValidators.NON_BLANK_VALIDATOR)
.dynamic(true)
.build(),
registeredService
);
}
private Map<String, String> createAttributes(final String lookupValue) {
Map<String, String> attributes = new HashMap<String, String>() {{
put(LOOKUP_ATTRIBUTE, lookupValue);
}};
return attributes;
}
}

View File

@ -34,6 +34,7 @@
<module>nifi-syslog-utils</module>
<module>nifi-database-utils</module>
<module>nifi-database-test-utils</module>
<module>nifi-service-utils</module>
</modules>
</project>

View File

@ -37,6 +37,11 @@
<artifactId>nifi-utils</artifactId>
<version>1.11.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-service-utils</artifactId>
<version>1.11.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>

View File

@ -16,27 +16,15 @@
*/
package org.apache.nifi.dbcp;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.annotation.behavior.DynamicProperty;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnDisabled;
import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.service.lookup.AbstractSingleAttributeBasedControllerServiceLookup;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Tags({ "dbcp", "jdbc", "database", "connection", "pooling", "store" })
@ -45,72 +33,23 @@ import java.util.Map;
"if the attribute is missing. The value of 'database.name' will be used to select the DBCPService that has been " +
"registered with that name. This will allow multiple DBCPServices to be defined and registered, and then selected " +
"dynamically at runtime by tagging flow files with the appropriate 'database.name' attribute.")
@DynamicProperty(name = "The ", value = "JDBC property value", expressionLanguageScope = ExpressionLanguageScope.NONE,
description = "")
public class DBCPConnectionPoolLookup extends AbstractControllerService implements DBCPService {
@DynamicProperty(name = "The name to register DBCPService", value = "The DBCPService",
description = "If '"+ DBCPConnectionPoolLookup.DATABASE_NAME_ATTRIBUTE +"' attribute contains " +
"the name of the dynamic property, then the DBCPService (registered in the value) will be selected.",
expressionLanguageScope = ExpressionLanguageScope.NONE)
public class DBCPConnectionPoolLookup
extends AbstractSingleAttributeBasedControllerServiceLookup<DBCPService> implements DBCPService {
public static final String DATABASE_NAME_ATTRIBUTE = "database.name";
private volatile Map<String,DBCPService> dbcpServiceMap;
@Override
protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
return new PropertyDescriptor.Builder()
.name(propertyDescriptorName)
.description("The DBCPService to return when database.name = '" + propertyDescriptorName + "'")
.identifiesControllerService(DBCPService.class)
.addValidator(StandardValidators.NON_BLANK_VALIDATOR)
.build();
protected String getLookupAttribute() {
return DATABASE_NAME_ATTRIBUTE;
}
@Override
protected Collection<ValidationResult> customValidate(ValidationContext context) {
final List<ValidationResult> results = new ArrayList<>();
int numDefinedServices = 0;
for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
if (descriptor.isDynamic()) {
numDefinedServices++;
}
final String referencedId = context.getProperty(descriptor).getValue();
if (this.getIdentifier().equals(referencedId)) {
results.add(new ValidationResult.Builder()
.subject(descriptor.getDisplayName())
.explanation("the current service cannot be registered as a DBCPService to lookup")
.valid(false)
.build());
}
}
if (numDefinedServices == 0) {
results.add(new ValidationResult.Builder()
.subject(this.getClass().getSimpleName())
.explanation("at least one DBCPService must be defined via dynamic properties")
.valid(false)
.build());
}
return results;
}
@OnEnabled
public void onEnabled(final ConfigurationContext context) {
final Map<String,DBCPService> serviceMap = new HashMap<>();
for (final PropertyDescriptor descriptor : context.getProperties().keySet()) {
if (descriptor.isDynamic()) {
final DBCPService dbcpService = context.getProperty(descriptor).asControllerService(DBCPService.class);
serviceMap.put(descriptor.getName(), dbcpService);
}
}
dbcpServiceMap = Collections.unmodifiableMap(serviceMap);
}
@OnDisabled
public void onDisabled() {
dbcpServiceMap = null;
public Class<DBCPService> getServiceType() {
return DBCPService.class;
}
@Override
@ -119,22 +58,7 @@ public class DBCPConnectionPoolLookup extends AbstractControllerService implemen
}
@Override
public Connection getConnection(Map<String, String> attributes) throws ProcessException {
if (!attributes.containsKey(DATABASE_NAME_ATTRIBUTE)) {
throw new ProcessException("Attributes must contain an attribute name '" + DATABASE_NAME_ATTRIBUTE + "'");
}
final String databaseName = attributes.get(DATABASE_NAME_ATTRIBUTE);
if (StringUtils.isBlank(databaseName)) {
throw new ProcessException(DATABASE_NAME_ATTRIBUTE + " cannot be null or blank");
}
final DBCPService dbcpService = dbcpServiceMap.get(databaseName);
if (dbcpService == null) {
throw new ProcessException("No DBCPService was found for database.name '" + databaseName + "'");
}
return dbcpService.getConnection(attributes);
public Connection getConnection(Map<String, String> attributes) {
return lookupService(attributes).getConnection(attributes);
}
}