diff --git a/nifi-api/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java b/nifi-api/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java new file mode 100644 index 0000000000..a3bbdf4deb --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/annotation/documentation/DeprecationNotice.java @@ -0,0 +1,46 @@ +/* + * 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.annotation.documentation; + +import org.apache.nifi.components.ConfigurableComponent; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that can be applied to a {@link org.apache.nifi.processor.Processor Processor}, + * {@link org.apache.nifi.controller.ControllerService ControllerService}, or + * {@link org.apache.nifi.reporting.ReportingTask ReportingTask} in order to + * warn about the deprecation of the component. The deprecation warning is informational only + * and doesn't affect the processor run time behavior in any way + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DeprecationNotice { + Class[] value() default {}; + + String[] classNames() default {}; + + String reason() default ""; +} + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java index 087e654374..ff3315dd7f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java @@ -39,6 +39,7 @@ public class ControllerServiceDTO extends ComponentDTO { private String state; private Boolean persistsState; private Boolean restricted; + private Boolean deprecated; private Boolean isExtensionMissing; private Boolean multipleVersionsAvailable; @@ -154,6 +155,20 @@ public class ControllerServiceDTO extends ComponentDTO { this.restricted = restricted; } + /** + * @return Whether the controller service has been deprecated. + */ + @ApiModelProperty( + value = "Whether the ontroller service has been deprecated." + ) + public Boolean getDeprecated() { + return deprecated; + } + + public void setDeprecated(Boolean deprecated) { + this.deprecated= deprecated; + } + /** * @return whether the underlying extension is missing */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java index e4da9dfe87..739d66270b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DocumentedTypeDTO.java @@ -34,6 +34,7 @@ public class DocumentedTypeDTO { private List controllerServiceApis; private String description; private String usageRestriction; + private String deprecationReason; private Set tags; /** @@ -64,6 +65,21 @@ public class DocumentedTypeDTO { this.usageRestriction = usageRestriction; } + /** + * @return An optional description of why the usage of this component is deprecated + */ + @ApiModelProperty( + value = "The description of why the usage of this component is restricted." + ) + public String getDeprecationReason() { + return deprecationReason; + } + + public void setDeprecationReason(String deprecationReason) { + this.deprecationReason = deprecationReason; + } + + /** * @return The type is the fully-qualified name of a Java class */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java index a119cea369..e7ba0a2bda 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorDTO.java @@ -41,6 +41,7 @@ public class ProcessorDTO extends ComponentDTO { private Boolean supportsBatching; private Boolean persistsState; private Boolean restricted; + private Boolean deprecated; private Boolean isExtensionMissing; private Boolean multipleVersionsAvailable; private String inputRequirement; @@ -200,6 +201,20 @@ public class ProcessorDTO extends ComponentDTO { this.restricted = restricted; } + /** + * @return Whether the processor has been deprecated. + */ + @ApiModelProperty( + value = "Whether the processor has been deprecated." + ) + public Boolean getDeprecated() { + return deprecated; + } + + public void setDeprecated(Boolean deprecated) { + this.deprecated = deprecated; + } + /** * @return the input requirement of this processor */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java index 56b63d9fa5..d44bf25a22 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ReportingTaskDTO.java @@ -35,6 +35,7 @@ public class ReportingTaskDTO extends ComponentDTO { private String comments; private Boolean persistsState; private Boolean restricted; + private Boolean deprecated; private Boolean isExtensionMissing; private Boolean multipleVersionsAvailable; @@ -153,6 +154,20 @@ public class ReportingTaskDTO extends ComponentDTO { this.restricted = restricted; } + /** + * @return Whether the reporting task has been deprecated. + */ + @ApiModelProperty( + value = "Whether the reporting task has been deprecated." + ) + public Boolean getDeprecated() { + return deprecated; + } + + public void setDeprecated(Boolean deprecated) { + this.deprecated= deprecated; + } + /** * @return whether the underlying extension is missing */ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java index e538fedff9..e5badc8222 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/main/java/org/apache/nifi/documentation/html/HtmlDocumentationWriter.java @@ -21,6 +21,7 @@ import org.apache.nifi.annotation.behavior.DynamicProperty; import org.apache.nifi.annotation.behavior.Restricted; import org.apache.nifi.annotation.behavior.Stateful; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.SeeAlso; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.bundle.Bundle; @@ -31,6 +32,7 @@ import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.controller.ControllerService; import org.apache.nifi.documentation.DocumentationWriter; import org.apache.nifi.nar.ExtensionManager; +import org.apache.nifi.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -136,6 +138,7 @@ public class HtmlDocumentationWriter implements DocumentationWriter { throws XMLStreamException { xmlStreamWriter.writeStartElement("body"); writeHeader(configurableComponent, xmlStreamWriter); + writeDeprecationWarning(configurableComponent, xmlStreamWriter); writeDescription(configurableComponent, xmlStreamWriter, hasAdditionalDetails); writeTags(configurableComponent, xmlStreamWriter); writeProperties(configurableComponent, xmlStreamWriter); @@ -216,6 +219,50 @@ public class HtmlDocumentationWriter implements DocumentationWriter { } } + /** + * Writes a warning about the deprecation of a component. + * + * @param configurableComponent the component to describe + * @param xmlStreamWriter the stream writer + * @throws XMLStreamException thrown if there was a problem writing to the + * XML stream + */ + private void writeDeprecationWarning(final ConfigurableComponent configurableComponent, + final XMLStreamWriter xmlStreamWriter) throws XMLStreamException { + final DeprecationNotice deprecationNotice = configurableComponent.getClass().getAnnotation(DeprecationNotice.class); + if (deprecationNotice != null) { + xmlStreamWriter.writeStartElement("h2"); + xmlStreamWriter.writeCharacters("Deprecation notice: "); + xmlStreamWriter.writeEndElement(); + xmlStreamWriter.writeStartElement("p"); + + xmlStreamWriter.writeCharacters(""); + if (!StringUtils.isEmpty(deprecationNotice.reason())) { + xmlStreamWriter.writeCharacters(deprecationNotice.reason()); + } else { + // Write a default note + xmlStreamWriter.writeCharacters("Please be aware this processor is deprecated and may be removed in " + + "the near future."); + } + xmlStreamWriter.writeEndElement(); + xmlStreamWriter.writeStartElement("p"); + xmlStreamWriter.writeCharacters("Please consider using one the following alternatives: "); + + + Class[] componentNames = deprecationNotice.value(); + String[] classNames = deprecationNotice.classNames(); + + if (componentNames.length > 0 || classNames.length > 0) { + // Write alternatives + iterateAndLinkComponents(xmlStreamWriter, componentNames, classNames, ","); + } else { + xmlStreamWriter.writeCharacters("No alternative components suggested."); + } + + xmlStreamWriter.writeEndElement(); + } + } + /** * Writes the list of components that may be linked from this component. * @@ -229,43 +276,16 @@ public class HtmlDocumentationWriter implements DocumentationWriter { if (seeAlso != null) { writeSimpleElement(xmlStreamWriter, "h3", "See Also:"); xmlStreamWriter.writeStartElement("p"); - int index = 0; - for (final Class linkedComponent : seeAlso.value()) { - if (index != 0) { - xmlStreamWriter.writeCharacters(", "); - } - writeLinkForComponent(xmlStreamWriter, linkedComponent); - - ++index; + Class[] componentNames = seeAlso.value(); + String[] classNames = seeAlso.classNames(); + if (componentNames.length > 0 || classNames.length > 0) { + // Write alternatives + iterateAndLinkComponents(xmlStreamWriter, componentNames, classNames, ", "); + } else { + xmlStreamWriter.writeCharacters("No tags provided."); } - for (final String linkedComponent : seeAlso.classNames()) { - if (index != 0) { - xmlStreamWriter.writeCharacters(", "); - } - - final List linkedComponentBundles = ExtensionManager.getBundles(linkedComponent); - - if (linkedComponentBundles != null && linkedComponentBundles.size() > 0) { - final Bundle firstBundle = linkedComponentBundles.get(0); - final BundleCoordinate firstCoordinate = firstBundle.getBundleDetails().getCoordinate(); - - final String group = firstCoordinate.getGroup(); - final String id = firstCoordinate.getId(); - final String version = firstCoordinate.getVersion(); - - final String link = "/nifi-docs/components/" + group + "/" + id + "/" + version + "/" + linkedComponent + "/index.html"; - - final int indexOfLastPeriod = linkedComponent.lastIndexOf(".") + 1; - - writeLink(xmlStreamWriter, linkedComponent.substring(indexOfLastPeriod), link); - - ++index; - } else { - LOGGER.warn("Could not link to {} because no bundles were found", new Object[] {linkedComponent}); - } - } xmlStreamWriter.writeEndElement(); } } @@ -295,7 +315,7 @@ public class HtmlDocumentationWriter implements DocumentationWriter { final String tagString = join(tags.value(), ", "); xmlStreamWriter.writeCharacters(tagString); } else { - xmlStreamWriter.writeCharacters("None."); + xmlStreamWriter.writeCharacters("No tags provided."); } xmlStreamWriter.writeEndElement(); } @@ -581,15 +601,16 @@ public class HtmlDocumentationWriter implements DocumentationWriter { xmlStreamWriter.writeEmptyElement("br"); xmlStreamWriter.writeCharacters(controllerServiceClass.getSimpleName()); - final List> implementations = lookupControllerServiceImpls(controllerServiceClass); + final List> implementationList = lookupControllerServiceImpls(controllerServiceClass); + + // Convert it into an array before proceeding + Class[] implementations = implementationList.stream().toArray(Class[]::new); + xmlStreamWriter.writeEmptyElement("br"); - if (implementations.size() > 0) { - final String title = implementations.size() > 1 ? "Implementations: " : "Implementation:"; + if (implementations.length > 0) { + final String title = implementations.length > 1 ? "Implementations: " : "Implementation:"; writeSimpleElement(xmlStreamWriter, "strong", title); - for (int i = 0; i < implementations.size(); i++) { - xmlStreamWriter.writeEmptyElement("br"); - writeLinkForComponent(xmlStreamWriter, implementations.get(i)); - } + iterateAndLinkComponents(xmlStreamWriter, implementations, null, "
"); } else { xmlStreamWriter.writeCharacters("No implementations found."); } @@ -673,32 +694,6 @@ public class HtmlDocumentationWriter implements DocumentationWriter { xmlStreamWriter.writeEndElement(); } - /** - * Writes a link to another configurable component - * - * @param xmlStreamWriter the xml stream writer - * @param clazz the configurable component to link to - * @throws XMLStreamException thrown if there is a problem writing the XML - */ - protected void writeLinkForComponent(final XMLStreamWriter xmlStreamWriter, final Class clazz) - throws XMLStreamException { - final String linkedComponentName = clazz.getName(); - final List linkedComponentBundles = ExtensionManager.getBundles(linkedComponentName); - - if (linkedComponentBundles != null && linkedComponentBundles.size() > 0) { - final Bundle firstLinkedComponentBundle = linkedComponentBundles.get(0); - final BundleCoordinate coordinate = firstLinkedComponentBundle.getBundleDetails().getCoordinate(); - - final String group = coordinate.getGroup(); - final String id = coordinate.getId(); - final String version = coordinate.getVersion(); - - writeLink(xmlStreamWriter, clazz.getSimpleName(), "/nifi-docs/components/" + group + "/" + id + "/" + version + "/" + clazz.getCanonicalName() + "/index.html"); - } else { - LOGGER.warn("Could not link to {} because no bundles were found", new Object[] {linkedComponentName}); - } - } - /** * Uses the {@link ExtensionManager} to discover any {@link ControllerService} implementations that implement a specific * ControllerService API. @@ -724,4 +719,94 @@ public class HtmlDocumentationWriter implements DocumentationWriter { return implementations; } + + /** + * Writes a link to another configurable component + * + * @param xmlStreamWriter the xml stream writer + * @param linkedComponents the array of configurable component to link to + * @param classNames the array of class names in string format to link to + * @param separator a separator used to split the values (in case more than 1. If the separator is enclosed in + * between "<" and ">" (.e.g "
" it is treated as a tag and written to the xmlStreamWriter as an + * empty tag + * @throws XMLStreamException thrown if there is a problem writing the XML + */ + protected void iterateAndLinkComponents(final XMLStreamWriter xmlStreamWriter, final Class[] linkedComponents, String[] classNames, String separator) + throws XMLStreamException { + + // Treat the the possible separators + boolean separatorIsElement; + + if (separator.startsWith("<") && separator.endsWith(">")) { + separatorIsElement = true; + } else { + separatorIsElement = false; + } + // Whatever the result, strip the possible < and > characters + separator = separator.replaceAll("\\<([^>]*)>","$1"); + + int index = 0; + for (final Class linkedComponent : linkedComponents ) { + final String linkedComponentName = linkedComponent.getName(); + final List linkedComponentBundles = ExtensionManager.getBundles(linkedComponentName); + if (linkedComponentBundles != null && linkedComponentBundles.size() > 0) { + final Bundle firstLinkedComponentBundle = linkedComponentBundles.get(0); + final BundleCoordinate coordinate = firstLinkedComponentBundle.getBundleDetails().getCoordinate(); + + final String group = coordinate.getGroup(); + final String id = coordinate.getId(); + final String version = coordinate.getVersion(); + + + + if (index != 0) { + if (separatorIsElement) { + xmlStreamWriter.writeEmptyElement(separator); + } else { + xmlStreamWriter.writeCharacters(separator); + } + } + writeLink(xmlStreamWriter, linkedComponent.getSimpleName(), "/nifi-docs/components/" + group + "/" + id + "/" + version + "/" + linkedComponent.getCanonicalName() + "/index.html"); + + ++index; + } else { + LOGGER.warn("Could not link to {} because no bundles were found", new Object[] {linkedComponentName}); + } + } + + if (classNames!= null) { + for (final String className : classNames) { + if (index != 0) { + if (separatorIsElement) { + xmlStreamWriter.writeEmptyElement(separator); + } else { + xmlStreamWriter.writeCharacters(separator); + } + } + + final List linkedComponentBundles = ExtensionManager.getBundles(className); + + if (linkedComponentBundles != null && linkedComponentBundles.size() > 0) { + final Bundle firstBundle = linkedComponentBundles.get(0); + final BundleCoordinate firstCoordinate = firstBundle.getBundleDetails().getCoordinate(); + + final String group = firstCoordinate.getGroup(); + final String id = firstCoordinate.getId(); + final String version = firstCoordinate.getVersion(); + + final String link = "/nifi-docs/components/" + group + "/" + id + "/" + version + "/" + className + "/index.html"; + + final int indexOfLastPeriod = className.lastIndexOf(".") + 1; + + writeLink(xmlStreamWriter, className.substring(indexOfLastPeriod), link); + + ++index; + } else { + LOGGER.warn("Could not link to {} because no bundles were found", new Object[] {className}); + } + } + } + + } + } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/DeprecatedProcessor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/DeprecatedProcessor.java new file mode 100644 index 0000000000..af44195820 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/example/DeprecatedProcessor.java @@ -0,0 +1,179 @@ +/* + * 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.example; + +import org.apache.nifi.annotation.behavior.DynamicProperty; +import org.apache.nifi.annotation.behavior.DynamicRelationship; +import org.apache.nifi.annotation.behavior.ReadsAttribute; +import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.annotation.behavior.WritesAttribute; +import org.apache.nifi.annotation.behavior.WritesAttributes; +import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.DeprecationNotice; +import org.apache.nifi.annotation.documentation.SeeAlso; +import org.apache.nifi.annotation.documentation.Tags; +import org.apache.nifi.annotation.lifecycle.OnRemoved; +import org.apache.nifi.annotation.lifecycle.OnShutdown; +import org.apache.nifi.components.AllowableValue; +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.components.state.Scope; +import org.apache.nifi.processor.AbstractProcessor; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.ProcessorInitializationContext; +import org.apache.nifi.processor.Relationship; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processor.util.StandardValidators; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Tags({"one", "two", "three"}) +@CapabilityDescription("This is a processor that is used to test documentation.") +@WritesAttributes({ + @WritesAttribute(attribute = "first", description = "this is the first attribute i write"), + @WritesAttribute(attribute = "second")}) +@ReadsAttribute(attribute = "incoming", description = "this specifies the format of the thing") +@SeeAlso(value = {FullyDocumentedControllerService.class, FullyDocumentedReportingTask.class}, classNames = {"org.apache.nifi.processor.ExampleProcessor"}) +@DynamicProperty(name = "Relationship Name", supportsExpressionLanguage = true, value = "some XPath", description = "Routes FlowFiles to relationships based on XPath") +@DynamicRelationship(name = "name from dynamic property", description = "all files that match the properties XPath") +@Stateful(scopes = {Scope.CLUSTER, Scope.LOCAL}, description = "state management description") +@Restricted("processor restriction description") +@DeprecationNotice({FullyDocumentedProcessor.class, FullyDocumentedReportingTask.class}) +public class DeprecatedProcessor extends AbstractProcessor { + + public static final PropertyDescriptor DIRECTORY = new PropertyDescriptor.Builder().name("Input Directory") + .description("The input directory from which to pull files").required(true) + .addValidator(StandardValidators.createDirectoryExistsValidator(true, false)) + .expressionLanguageSupported(true).build(); + + public static final PropertyDescriptor RECURSE = new PropertyDescriptor.Builder().name("Recurse Subdirectories") + .description("Indicates whether or not to pull files from subdirectories").required(true) + .allowableValues( + new AllowableValue("true", "true", "Should pull from sub directories"), + new AllowableValue("false", "false", "Should not pull from sub directories") + ).defaultValue("true").build(); + + public static final PropertyDescriptor POLLING_INTERVAL = new PropertyDescriptor.Builder().name("Polling Interval") + .description("Indicates how long to wait before performing a directory listing").required(true) + .addValidator(StandardValidators.TIME_PERIOD_VALIDATOR).defaultValue("0 sec").build(); + + public static final PropertyDescriptor OPTIONAL_PROPERTY = new PropertyDescriptor.Builder() + .name("Optional Property").description("This is a property you can use or not").required(false).build(); + + public static final PropertyDescriptor TYPE_PROPERTY = new PropertyDescriptor.Builder() + .name("Type") + .description("This is the type of something that you can choose. It has several possible values") + .allowableValues("yes", "no", "maybe", "possibly", "not likely", "longer option name") + .required(true).build(); + + public static final PropertyDescriptor SERVICE_PROPERTY = new PropertyDescriptor.Builder() + .name("Controller Service").description("This is the controller service to use to do things") + .identifiesControllerService(SampleService.class).required(true).build(); + + public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success") + .description("Successful files").build(); + public static final Relationship REL_FAILURE = new Relationship.Builder().name("failure") + .description("Failing files").build(); + + private List properties; + private Set relationships; + + private int onRemovedNoArgs = 0; + private int onRemovedArgs = 0; + + private int onShutdownNoArgs = 0; + private int onShutdownArgs = 0; + + @Override + protected void init(ProcessorInitializationContext context) { + final List properties = new ArrayList<>(); + properties.add(DIRECTORY); + properties.add(RECURSE); + properties.add(POLLING_INTERVAL); + properties.add(OPTIONAL_PROPERTY); + properties.add(TYPE_PROPERTY); + properties.add(SERVICE_PROPERTY); + this.properties = Collections.unmodifiableList(properties); + + final Set relationships = new HashSet<>(); + relationships.add(REL_SUCCESS); + relationships.add(REL_FAILURE); + this.relationships = Collections.unmodifiableSet(relationships); + } + + @Override + protected List getSupportedPropertyDescriptors() { + return properties; + } + + @Override + public Set getRelationships() { + return relationships; + } + + @Override + public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException { + + } + + @Override + protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(String propertyDescriptorName) { + return new PropertyDescriptor.Builder().name(propertyDescriptorName) + .description("This is a property you can use or not").dynamic(true).build(); + } + + @OnRemoved + public void onRemovedNoArgs() { + onRemovedNoArgs++; + } + + @OnRemoved + public void onRemovedArgs(ProcessContext context) { + onRemovedArgs++; + } + + @OnShutdown + public void onShutdownNoArgs() { + onShutdownNoArgs++; + } + + @OnShutdown + public void onShutdownArgs(ProcessContext context) { + onShutdownArgs++; + } + + public int getOnRemovedNoArgs() { + return onRemovedNoArgs; + } + + public int getOnRemovedArgs() { + return onRemovedArgs; + } + + public int getOnShutdownNoArgs() { + return onShutdownNoArgs; + } + + public int getOnShutdownArgs() { + return onShutdownArgs; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java index ddb332560e..c6d867a90f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-documentation/src/test/java/org/apache/nifi/documentation/html/ProcessorDocumentationWriterTest.java @@ -18,6 +18,7 @@ package org.apache.nifi.documentation.html; import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.documentation.DocumentationWriter; +import org.apache.nifi.documentation.example.DeprecatedProcessor; import org.apache.nifi.documentation.example.FullyDocumentedProcessor; import org.apache.nifi.documentation.example.NakedProcessor; import org.apache.nifi.documentation.example.ProcessorWithLogger; @@ -76,7 +77,8 @@ public class ProcessorDocumentationWriterTest { .value()); assertNotContains(results, "This component has no required or optional properties."); assertNotContains(results, "No description provided."); - assertNotContains(results, "No Tags provided."); + + assertNotContains(results, "No tags provided."); assertNotContains(results, "Additional Details..."); // verify the right OnRemoved and OnShutdown methods were called @@ -107,7 +109,7 @@ public class ProcessorDocumentationWriterTest { assertContains(results, "No description provided."); // no tags - assertContains(results, "None."); + assertContains(results, "No tags provided."); // properties assertContains(results, "This component has no required or optional properties."); @@ -139,4 +141,63 @@ public class ProcessorDocumentationWriterTest { XmlValidator.assertXmlValid(results); } + + @Test + public void testDeprecatedProcessor() throws IOException { + DeprecatedProcessor processor = new DeprecatedProcessor(); + ProcessorInitializer initializer = new ProcessorInitializer(); + initializer.initialize(processor); + + DocumentationWriter writer = new HtmlProcessorDocumentationWriter(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + writer.write(processor, baos, false); + initializer.teardown(processor); + + String results = new String(baos.toByteArray()); + XmlValidator.assertXmlValid(results); + + assertContains(results, DeprecatedProcessor.DIRECTORY.getDisplayName()); + assertContains(results, DeprecatedProcessor.DIRECTORY.getDescription()); + assertContains(results, DeprecatedProcessor.OPTIONAL_PROPERTY.getDisplayName()); + assertContains(results, DeprecatedProcessor.OPTIONAL_PROPERTY.getDescription()); + assertContains(results, DeprecatedProcessor.POLLING_INTERVAL.getDisplayName()); + assertContains(results, DeprecatedProcessor.POLLING_INTERVAL.getDescription()); + assertContains(results, DeprecatedProcessor.POLLING_INTERVAL.getDefaultValue()); + assertContains(results, DeprecatedProcessor.RECURSE.getDisplayName()); + assertContains(results, DeprecatedProcessor.RECURSE.getDescription()); + + assertContains(results, DeprecatedProcessor.REL_SUCCESS.getName()); + assertContains(results, DeprecatedProcessor.REL_SUCCESS.getDescription()); + assertContains(results, DeprecatedProcessor.REL_FAILURE.getName()); + assertContains(results, DeprecatedProcessor.REL_FAILURE.getDescription()); + assertContains(results, "Controller Service API: "); + assertContains(results, "SampleService"); + + assertContains(results, "CLUSTER, LOCAL"); + assertContains(results, "state management description"); + + assertContains(results, "processor restriction description"); + + assertNotContains(results, "iconSecure.png"); + assertContains(results, DeprecatedProcessor.class.getAnnotation(CapabilityDescription.class) + .value()); + + // Check for the existence of deprecation notice + assertContains(results, "Deprecation notice: "); + // assertContains(results, DeprecatedProcessor.class.getAnnotation(DeprecationNotice.class.)); + + assertNotContains(results, "This component has no required or optional properties."); + assertNotContains(results, "No description provided."); + assertNotContains(results, "No tags provided."); + assertNotContains(results, "Additional Details..."); + + // verify the right OnRemoved and OnShutdown methods were called + Assert.assertEquals(0, processor.getOnRemovedArgs()); + Assert.assertEquals(0, processor.getOnRemovedNoArgs()); + + Assert.assertEquals(1, processor.getOnShutdownArgs()); + Assert.assertEquals(1, processor.getOnShutdownNoArgs()); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java index 2ccb858c47..5888419acf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ConfiguredComponent.java @@ -89,6 +89,11 @@ public interface ConfiguredComponent extends ComponentAuthorizable { */ boolean isRestricted(); + /** + * @return whether or not the underlying implementation is deprecated + */ + boolean isDeprecated(); + @Override default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map resourceContext) { // if this is a modification request and the reporting task is restricted ensure the user has elevated privileges. if this diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java index f42321e647..7b3ef76138 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java @@ -25,6 +25,7 @@ import org.apache.nifi.annotation.behavior.TriggerWhenAnyDestinationAvailable; import org.apache.nifi.annotation.behavior.TriggerWhenEmpty; import org.apache.nifi.annotation.configuration.DefaultSchedule; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.lifecycle.OnScheduled; import org.apache.nifi.annotation.lifecycle.OnStopped; import org.apache.nifi.annotation.lifecycle.OnUnscheduled; @@ -239,6 +240,12 @@ public class StandardProcessorNode extends ProcessorNode implements Connectable return getProcessor().getClass().isAnnotationPresent(Restricted.class); } + @Override + public boolean isDeprecated() { + return getProcessor().getClass().isAnnotationPresent(DeprecationNotice.class); + } + + /** * Provides and opportunity to retain information about this particular * processor instance diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java index 3dbfce193e..53b7d15815 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java @@ -17,6 +17,7 @@ package org.apache.nifi.controller.reporting; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.authorization.Resource; import org.apache.nifi.authorization.resource.Authorizable; import org.apache.nifi.authorization.resource.ResourceFactory; @@ -66,6 +67,11 @@ public class StandardReportingTaskNode extends AbstractReportingTaskNode impleme return getReportingTask().getClass().isAnnotationPresent(Restricted.class); } + @Override + public boolean isDeprecated() { + return getReportingTask().getClass().isAnnotationPresent(DeprecationNotice.class); + } + @Override public ReportingContext getReportingContext() { return new StandardReportingContext(flowController, flowController.getBulletinRepository(), getProperties(), flowController, getReportingTask(), getVariableRegistry()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java index 7a744b778e..c7eaa7fa12 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java @@ -18,6 +18,7 @@ package org.apache.nifi.controller.service; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.annotation.behavior.Restricted; +import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.lifecycle.OnDisabled; import org.apache.nifi.annotation.lifecycle.OnEnabled; import org.apache.nifi.authorization.Resource; @@ -146,6 +147,11 @@ public class StandardControllerServiceNode extends AbstractConfiguredComponent i return getControllerServiceImplementation().getClass().isAnnotationPresent(Restricted.class); } + @Override + public boolean isDeprecated() { + return getControllerServiceImplementation().getClass().isAnnotationPresent(DeprecationNotice.class); + } + @Override public ControllerService getControllerServiceImplementation() { return controllerServiceHolder.get().getImplementation(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 51548118be..95b1ebcd2e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -36,6 +36,7 @@ import org.apache.nifi.action.details.PurgeDetails; import org.apache.nifi.annotation.behavior.Restricted; import org.apache.nifi.annotation.behavior.Stateful; import org.apache.nifi.annotation.documentation.CapabilityDescription; +import org.apache.nifi.annotation.documentation.DeprecationNotice; import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer; import org.apache.nifi.authorization.AccessPolicy; @@ -1254,6 +1255,7 @@ public final class DtoFactory { dto.setComments(reportingTaskNode.getComments()); dto.setPersistsState(reportingTaskNode.getReportingTask().getClass().isAnnotationPresent(Stateful.class)); dto.setRestricted(reportingTaskNode.isRestricted()); + dto.setDeprecated(reportingTaskNode.isDeprecated()); dto.setExtensionMissing(reportingTaskNode.isExtensionMissing()); dto.setMultipleVersionsAvailable(compatibleBundles.size() > 1); @@ -1334,6 +1336,7 @@ public final class DtoFactory { dto.setComments(controllerServiceNode.getComments()); dto.setPersistsState(controllerServiceNode.getControllerServiceImplementation().getClass().isAnnotationPresent(Stateful.class)); dto.setRestricted(controllerServiceNode.isRestricted()); + dto.setDeprecated(controllerServiceNode.isDeprecated()); dto.setExtensionMissing(controllerServiceNode.isExtensionMissing()); dto.setMultipleVersionsAvailable(compatibleBundles.size() > 1); @@ -2062,6 +2065,11 @@ public final class DtoFactory { return restriction == null ? null : restriction.value(); } + private String getDeprecationReason(final Class cls) { + final DeprecationNotice deprecationNotice = cls.getAnnotation(DeprecationNotice.class); + return deprecationNotice == null ? null : deprecationNotice.reason(); + } + /** * Gets the capability description from the specified class. */ @@ -2164,6 +2172,7 @@ public final class DtoFactory { dto.setControllerServiceApis(createControllerServiceApiDto(cls)); dto.setDescription(getCapabilityDescription(cls)); dto.setUsageRestriction(getUsageRestriction(cls)); + dto.setDeprecationReason(getDeprecationReason(cls)); dto.setTags(getTags(cls)); types.add(dto); } @@ -2213,6 +2222,7 @@ public final class DtoFactory { dto.setInputRequirement(node.getInputRequirement().name()); dto.setPersistsState(node.getProcessor().getClass().isAnnotationPresent(Stateful.class)); dto.setRestricted(node.isRestricted()); + dto.setDeprecated(node.isDeprecated()); dto.setExtensionMissing(node.isExtensionMissing()); dto.setMultipleVersionsAvailable(compatibleBundles.size() > 1);