blob: ed204ec90db49191f71e62f9c55dcb4df1565944 [file] [log] [blame]
/*
* 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();
}
}