blob: 727b097b1d1dd28639c21e9964a568ae96263f55 [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 and
* limitations under the License.
*/
package org.apache.nifi.documentation;
import org.apache.nifi.annotation.behavior.DynamicProperties;
import org.apache.nifi.annotation.behavior.DynamicProperty;
import org.apache.nifi.annotation.behavior.DynamicRelationship;
import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.PrimaryNodeOnly;
import org.apache.nifi.annotation.behavior.ReadsAttribute;
import org.apache.nifi.annotation.behavior.ReadsAttributes;
import org.apache.nifi.annotation.behavior.Restricted;
import org.apache.nifi.annotation.behavior.SideEffectFree;
import org.apache.nifi.annotation.behavior.Stateful;
import org.apache.nifi.annotation.behavior.SupportsBatching;
import org.apache.nifi.annotation.behavior.SupportsSensitiveDynamicProperties;
import org.apache.nifi.annotation.behavior.SystemResourceConsideration;
import org.apache.nifi.annotation.behavior.TriggerSerially;
import org.apache.nifi.annotation.behavior.TriggerWhenAnyDestinationAvailable;
import org.apache.nifi.annotation.behavior.TriggerWhenEmpty;
import org.apache.nifi.annotation.behavior.WritesAttribute;
import org.apache.nifi.annotation.behavior.WritesAttributes;
import org.apache.nifi.annotation.configuration.DefaultSchedule;
import org.apache.nifi.annotation.configuration.DefaultSettings;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.DeprecationNotice;
import org.apache.nifi.annotation.documentation.MultiProcessorUseCase;
import org.apache.nifi.annotation.documentation.SeeAlso;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.documentation.UseCase;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.documentation.init.DocumentationControllerServiceInitializationContext;
import org.apache.nifi.documentation.init.DocumentationFlowAnalysisRuleInitializationContext;
import org.apache.nifi.documentation.init.DocumentationParameterProviderInitializationContext;
import org.apache.nifi.documentation.init.DocumentationProcessorInitializationContext;
import org.apache.nifi.documentation.init.DocumentationReportingInitializationContext;
import org.apache.nifi.flowanalysis.FlowAnalysisRule;
import org.apache.nifi.parameter.ParameterProvider;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.reporting.ReportingTask;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Base class for DocumentationWriter that simplifies iterating over all information for a component, creating a separate method
* for each, to ensure that implementations properly override all methods and therefore properly account for all information about
* a component.
*
* Please note that while this class lives within the nifi-api, it is provided primarily as a means for documentation components within
* the NiFi NAR Maven Plugin. Its home is the nifi-api, however, because the API is needed in order to extract the relevant information and
* the NAR Maven Plugin cannot have a direct dependency on nifi-api (doing so would cause a circular dependency). By having this homed within
* the nifi-api, the Maven plugin is able to discover the class dynamically and invoke the one or two methods necessary to create the documentation.
*
* This is a new capability in 1.9.0 in preparation for the Extension Registry and therefore, you should
* <b>NOTE WELL:</b> At this time, while this class is part of nifi-api, it is still evolving and may change in a non-backward-compatible manner or even be
* removed from one incremental release to the next. Use at your own risk!
*/
public abstract class AbstractDocumentationWriter implements ExtensionDocumentationWriter {
@Override
public void initialize(final ConfigurableComponent component) {
try {
if (component instanceof Processor) {
initialize((Processor) component);
} else if (component instanceof ControllerService) {
initialize((ControllerService) component);
} else if (component instanceof ReportingTask) {
initialize((ReportingTask) component);
} else if (component instanceof FlowAnalysisRule) {
initialize((FlowAnalysisRule) component);
} else if (component instanceof ParameterProvider) {
initialize((ParameterProvider) component);
}
} catch (final InitializationException ie) {
throw new RuntimeException("Failed to initialize " + component, ie);
}
}
protected void initialize(final Processor processor) {
processor.initialize(new DocumentationProcessorInitializationContext());
}
protected void initialize(final ControllerService service) throws InitializationException {
service.initialize(new DocumentationControllerServiceInitializationContext());
}
protected void initialize(final ReportingTask reportingTask) throws InitializationException {
reportingTask.initialize(new DocumentationReportingInitializationContext());
}
protected void initialize(final FlowAnalysisRule flowAnalysisRule) throws InitializationException {
flowAnalysisRule.initialize(new DocumentationFlowAnalysisRuleInitializationContext());
}
protected void initialize(final ParameterProvider parameterProvider) throws InitializationException {
parameterProvider.initialize(new DocumentationParameterProviderInitializationContext());
}
@Override
public final void write(final ConfigurableComponent component) throws IOException {
write(component, Collections.emptyList(), Collections.emptyMap());
}
@Override
public final void write(final ConfigurableComponent component, final Collection<ServiceAPI> providedServices, Map<String, ServiceAPI> propertyServices) throws IOException {
writeHeader(component);
writeBody(component, propertyServices);
if (providedServices != null && component instanceof ControllerService) {
writeProvidedServices(providedServices);
}
writeFooter(component);
}
protected void writeBody(final ConfigurableComponent component, Map<String, ServiceAPI> propertyServices) throws IOException {
writeExtensionName(component.getClass().getName());
writeExtensionType(getExtensionType(component));
writeDeprecationNotice(component.getClass().getAnnotation(DeprecationNotice.class));
writeDescription(getDescription(component));
writeTags(getTags(component));
writeProperties(component.getPropertyDescriptors(), propertyServices);
writeDynamicProperties(getDynamicProperties(component));
writeSupportsSensitiveDynamicProperties(component.getClass().getAnnotation(SupportsSensitiveDynamicProperties.class));
if (component instanceof Processor) {
final Processor processor = (Processor) component;
writeRelationships(processor.getRelationships());
writeDynamicRelationship(getDynamicRelationship(processor));
writeReadsAttributes(getReadsAttributes(processor));
writeWritesAttributes(getWritesAttributes(processor));
writeTriggerSerially(processor.getClass().getAnnotation(TriggerSerially.class));
writeTriggerWhenEmpty(processor.getClass().getAnnotation(TriggerWhenEmpty.class));
writeTriggerWhenAnyDestinationAvailable(processor.getClass().getAnnotation(TriggerWhenAnyDestinationAvailable.class));
writeSupportsBatching(processor.getClass().getAnnotation(SupportsBatching.class));
writePrimaryNodeOnly(processor.getClass().getAnnotation(PrimaryNodeOnly.class));
writeSideEffectFree(processor.getClass().getAnnotation(SideEffectFree.class));
writeDefaultSettings(processor.getClass().getAnnotation(DefaultSettings.class));
}
writeStatefulInfo(component.getClass().getAnnotation(Stateful.class));
writeRestrictedInfo(component.getClass().getAnnotation(Restricted.class));
writeInputRequirementInfo(getInputRequirement(component));
writeSystemResourceConsiderationInfo(getSystemResourceConsiderations(component));
writeUseCases(getUseCases(component));
writeMultiProcessorUseCases(getMultiProcessorUseCases(component));
writeSeeAlso(component.getClass().getAnnotation(SeeAlso.class));
writeDefaultSchedule(component.getClass().getAnnotation(DefaultSchedule.class));
}
protected String getDescription(final ConfigurableComponent component) {
final CapabilityDescription capabilityDescription = component.getClass().getAnnotation(CapabilityDescription.class);
if (capabilityDescription == null) {
return null;
}
return capabilityDescription.value();
}
protected List<String> getTags(final ConfigurableComponent component) {
final Tags tags = component.getClass().getAnnotation(Tags.class);
if (tags == null) {
return Collections.emptyList();
}
final String[] tagValues = tags.value();
return tagValues == null ? Collections.emptyList() : Arrays.asList(tagValues);
}
protected List<DynamicProperty> getDynamicProperties(ConfigurableComponent configurableComponent) {
final List<DynamicProperty> dynamicProperties = new ArrayList<>();
final DynamicProperties dynProps = configurableComponent.getClass().getAnnotation(DynamicProperties.class);
if (dynProps != null) {
Collections.addAll(dynamicProperties, dynProps.value());
}
final DynamicProperty dynProp = configurableComponent.getClass().getAnnotation(DynamicProperty.class);
if (dynProp != null) {
dynamicProperties.add(dynProp);
}
return dynamicProperties;
}
private DynamicRelationship getDynamicRelationship(Processor processor) {
return processor.getClass().getAnnotation(DynamicRelationship.class);
}
private List<ReadsAttribute> getReadsAttributes(final Processor processor) {
final List<ReadsAttribute> attributes = new ArrayList<>();
final ReadsAttributes readsAttributes = processor.getClass().getAnnotation(ReadsAttributes.class);
if (readsAttributes != null) {
Collections.addAll(attributes, readsAttributes.value());
}
final ReadsAttribute readsAttribute = processor.getClass().getAnnotation(ReadsAttribute.class);
if (readsAttribute != null) {
attributes.add(readsAttribute);
}
return attributes;
}
private List<WritesAttribute> getWritesAttributes(Processor processor) {
List<WritesAttribute> attributes = new ArrayList<>();
WritesAttributes writesAttributes = processor.getClass().getAnnotation(WritesAttributes.class);
if (writesAttributes != null) {
Collections.addAll(attributes, writesAttributes.value());
}
WritesAttribute writeAttribute = processor.getClass().getAnnotation(WritesAttribute.class);
if (writeAttribute != null) {
attributes.add(writeAttribute);
}
return attributes;
}
private InputRequirement.Requirement getInputRequirement(final ConfigurableComponent component) {
final InputRequirement annotation = component.getClass().getAnnotation(InputRequirement.class);
return annotation == null ? null : annotation.value();
}
private List<SystemResourceConsideration> getSystemResourceConsiderations(final ConfigurableComponent component) {
SystemResourceConsideration[] systemResourceConsiderations = component.getClass().getAnnotationsByType(SystemResourceConsideration.class);
if (systemResourceConsiderations.length == 0) {
return Collections.emptyList();
}
return Arrays.asList(systemResourceConsiderations);
}
private List<UseCase> getUseCases(final ConfigurableComponent component) {
UseCase[] useCases = component.getClass().getAnnotationsByType(UseCase.class);
if (useCases.length == 0) {
return Collections.emptyList();
}
return Arrays.asList(useCases);
}
private List<MultiProcessorUseCase> getMultiProcessorUseCases(final ConfigurableComponent component) {
MultiProcessorUseCase[] useCases = component.getClass().getAnnotationsByType(MultiProcessorUseCase.class);
if (useCases.length == 0) {
return Collections.emptyList();
}
return Arrays.asList(useCases);
}
protected ExtensionType getExtensionType(final ConfigurableComponent component) {
if (component instanceof Processor) {
return ExtensionType.PROCESSOR;
}
if (component instanceof ControllerService) {
return ExtensionType.CONTROLLER_SERVICE;
}
if (component instanceof ReportingTask) {
return ExtensionType.REPORTING_TASK;
}
if (component instanceof FlowAnalysisRule) {
return ExtensionType.FLOW_ANALYSIS_RULE;
}
if (component instanceof ParameterProvider) {
return ExtensionType.PARAMETER_PROVIDER;
}
throw new AssertionError("Encountered unknown Configurable Component Type for " + component);
}
protected abstract void writeHeader(ConfigurableComponent component) throws IOException;
protected abstract void writeExtensionName(String extensionName) throws IOException;
protected abstract void writeExtensionType(ExtensionType extensionType) throws IOException;
protected abstract void writeDeprecationNotice(final DeprecationNotice deprecationNotice) throws IOException;
protected abstract void writeDescription(String description) throws IOException;
protected abstract void writeTags(List<String> tags) throws IOException;
protected abstract void writeProperties(List<PropertyDescriptor> properties, Map<String, ServiceAPI> propertyServices) throws IOException;
protected abstract void writeDynamicProperties(List<DynamicProperty> dynamicProperties) throws IOException;
protected abstract void writeStatefulInfo(Stateful stateful) throws IOException;
protected abstract void writeRestrictedInfo(Restricted restricted) throws IOException;
protected abstract void writeInputRequirementInfo(InputRequirement.Requirement requirement) throws IOException;
protected abstract void writeSystemResourceConsiderationInfo(List<SystemResourceConsideration> considerations) throws IOException;
protected abstract void writeSeeAlso(SeeAlso seeAlso) throws IOException;
protected abstract void writeUseCases(List<UseCase> useCases) throws IOException;
protected abstract void writeMultiProcessorUseCases(List<MultiProcessorUseCase> useCases) throws IOException;
protected abstract void writeDefaultSchedule(DefaultSchedule defaultSchedule) throws IOException;
// Processor-specific methods
protected abstract void writeRelationships(Set<Relationship> relationships) throws IOException;
protected abstract void writeDynamicRelationship(DynamicRelationship dynamicRelationship) throws IOException;
protected abstract void writeReadsAttributes(List<ReadsAttribute> attributes) throws IOException;
protected abstract void writeWritesAttributes(List<WritesAttribute> attributes) throws IOException;
protected abstract void writeTriggerSerially(TriggerSerially triggerSerially) throws IOException;
protected abstract void writeTriggerWhenEmpty(TriggerWhenEmpty triggerWhenEmpty) throws IOException;
protected abstract void writeTriggerWhenAnyDestinationAvailable(TriggerWhenAnyDestinationAvailable triggerWhenAnyDestinationAvailable) throws IOException;
protected abstract void writeSupportsBatching(SupportsBatching supportsBatching) throws IOException;
protected abstract void writeSupportsSensitiveDynamicProperties(SupportsSensitiveDynamicProperties supportsSensitiveDynamicProperties) throws IOException;
protected abstract void writePrimaryNodeOnly(PrimaryNodeOnly primaryNodeOnly) throws IOException;
protected abstract void writeSideEffectFree(SideEffectFree sideEffectFree) throws IOException;
protected abstract void writeDefaultSettings(DefaultSettings defaultSettings) throws IOException;
// ControllerService-specific methods
protected abstract void writeProvidedServices(Collection<ServiceAPI> providedServices) throws IOException;
protected abstract void writeFooter(ConfigurableComponent component) throws IOException;
}