NIFI-3761

- adjust testFullyDocumentedProcessor to correctly eval to false when needed
- Introduce the ability to deprecate a component
- Allow documentation to inform user about deprecation of a component
- This closes #1718

NIFI-391 - Add set/getDeprecationReson to DocumentedDTO and use it within DtoFactory'
This commit is contained in:
Andre F de Miranda 2017-04-29 13:09:22 +10:00 committed by Matt Gilman
parent 3353865ce9
commit d092551211
No known key found for this signature in database
GPG Key ID: DF61EC19432AEE37
13 changed files with 536 additions and 70 deletions

View File

@ -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<? extends ConfigurableComponent>[] value() default {};
String[] classNames() default {};
String reason() default "";
}

View File

@ -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
*/

View File

@ -34,6 +34,7 @@ public class DocumentedTypeDTO {
private List<ControllerServiceApiDTO> controllerServiceApis;
private String description;
private String usageRestriction;
private String deprecationReason;
private Set<String> 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
*/

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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<? extends ConfigurableComponent>[] 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<? extends ConfigurableComponent> linkedComponent : seeAlso.value()) {
if (index != 0) {
xmlStreamWriter.writeCharacters(", ");
}
writeLinkForComponent(xmlStreamWriter, linkedComponent);
++index;
Class<? extends ConfigurableComponent>[] 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<Bundle> 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<Class<? extends ControllerService>> implementations = lookupControllerServiceImpls(controllerServiceClass);
final List<Class<? extends ControllerService>> implementationList = lookupControllerServiceImpls(controllerServiceClass);
// Convert it into an array before proceeding
Class<? extends ControllerService>[] 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, "<br>");
} 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<Bundle> 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 "<br>" 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<? extends ConfigurableComponent>[] 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<? extends ConfigurableComponent> linkedComponent : linkedComponents ) {
final String linkedComponentName = linkedComponent.getName();
final List<Bundle> 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<Bundle> 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});
}
}
}
}
}

View File

@ -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<PropertyDescriptor> properties;
private Set<Relationship> 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<PropertyDescriptor> 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<Relationship> relationships = new HashSet<>();
relationships.add(REL_SUCCESS);
relationships.add(REL_FAILURE);
this.relationships = Collections.unmodifiableSet(relationships);
}
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return properties;
}
@Override
public Set<Relationship> 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;
}
}

View File

@ -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());
}
}

View File

@ -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<String, String> resourceContext) {
// if this is a modification request and the reporting task is restricted ensure the user has elevated privileges. if this

View File

@ -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

View File

@ -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());

View File

@ -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();

View File

@ -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);