Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanded plugin constructor capabilities #481

Merged

Conversation

dlvenable
Copy link
Member

@dlvenable dlvenable commented Oct 29, 2021

Description

  • Introduces the @DataPrepperPluginConstructor annotation
  • When creating a plugin instance, use the @DataPrepperPluginConstructor annotated-constructor. If not, then use the constructor which takes the PluginSetting or configured model class. Finally, use the default constructor as the last option.
  • The plugin framework can also inject a PluginMetrics object in the constructor.
  • Updated some of the simple plugins to use the default constructor where appropriate.
  • Updated HTTPSource to use a configuration model. It is provided into the constructor, and the PluginMetrics is also now injected.

Issues Resolved

This has some of the proposed functionality from #469.

Check List

  • New functionality includes testing.
    • All tests pass
  • New functionality has been documented.
    • New functionality has javadoc added
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

* This exists so that this class can still exhibit the correct behavior when creating new plugin
* classes. This whole class is going to be deleted in the next major version, as will this method.
*/
public static void dangerousMethod_setPluginFunction(final BiFunction<PluginSetting, Class<?>, Object> newPluginFunction) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to call this method out. This class is in the common package, but will be removed. Because the implementation for creating plugins is now in data-prepper-core, this class cannot use those classes. So, I created this as a way to allow this deprecated class to generate plugins the same way at that the new framework does.

As this class is going away, I felt fine with this quick hack. An alternative would be to remove all the old plugin code now rather than in v2.0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with this as well because of the reasons you provided. Thanks!

@dlvenable dlvenable requested a review from cmanning09 October 29, 2021 19:41
private PluginArgumentsContext(final Builder builder) {
typedArgumentsSuppliers = new HashMap<>();

Objects.requireNonNull(builder.pluginSetting);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's include an error message here.

private Object pluginConfiguration;
private PluginSetting pluginSetting;

Builder withPluginConfiguration(final Object pluginConfiguration) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a pluginConfiguration?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 68 to 72
String.format("Data Prepper plugin requires a constructor which is either: " +
"1. Annotated with @DataPrepperPlugin; " +
"2. Contains a single argument of type %s; or " +
"3. Has a default constructor. " +
"Plugin %s with name %s is missing such constructor.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are putting the solution to an error before providing the context for the error. I would recommend putting "Plugin %s with name %s is missing such constructor." first and then the solution.

I would also recommend the solution have a stronger call to action rather than stating the requirements. "Please ensure the plugin's constructor is either:"


class PluginCreator {
private static final Logger LOG = LoggerFactory.getLogger(PluginCreator.class);

<T> T newPluginInstance(final Class<T> pluginClass,
final Object pluginConfiguration,
final PluginArgumentsContext pluginConstructionContext,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want to refer to this as a pluginConstructionContext?

I would recommend just keeping it as pluginArgumentsContext.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through a few class names and it appears that IntelliJ didn't pick up renaming the parameter here. I'll fix this up to match.

*/
class PluginArgumentsContext {
private final Map<Class<?>, Supplier<Object>> typedArgumentsSuppliers;
private final Class<?> singleParameterObject;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's be consistent on naming here and refer to this as an argument. singleArgument


if(builder.pluginConfiguration != null) {
typedArgumentsSuppliers.put(builder.pluginConfiguration.getClass(), () -> builder.pluginConfiguration);
singleParameterObject = builder.pluginConfiguration.getClass();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this singleParameterObject ever be used when a builder.pluginConfiguration is provided?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the singleParameterObject is going to be the custom POJO type or the PluginSetting.

return this;
}

Builder withPluginSetting(final PluginSetting pluginSetting) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some nuance to how the parameters impact how the plugins are loaded. I really wish we had plugin documentation. I am worried we are going to build out the plugin documentation at the very end and miss some details. Is there any way we can start building out documentation of this framework now? It doesn't have to be perfect or finalized. Just a README in this package would be nice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I can add this next.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the documentation and linked it from the Development guide. As plugin support is extended, I would like to reconsidering the linking, but I believe this is sufficient for now.

singleParameterObject = builder.pluginSetting.getClass();
}

typedArgumentsSuppliers.put(PluginMetrics.class, () -> PluginMetrics.fromPluginSetting(builder.pluginSetting));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this only be added if the pluginMetrics value is non null?

Also, are we supporting an annotated constructor like pulbic SomePlugin(final PluginSettings pluginSettings, final PluginMetrics pluginMetrics) {...}?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer your second question, yes, we are support that type of constructor. But, only if it is annotated with @DataPrepperConstructor. The HTTPSource shows something very similar to this.

I'm not quite sure what your first question is asking. At this point of the code we are only adding a Supplier. So, no PluginMetric is constructor unless the annotated constructor requests it.

final String error =
String.format("Data Prepper plugin requires a constructor which is either: " +
"1. Annotated with @DataPrepperPlugin; " +
"2. Contains a single argument of type %s; or " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if there are two separate single argument constructors? What is the failure mode?

What is your vision for v2.0? Do you plan on supporting all of these scenarios in the future as well? Would it be possible to move to an explicit model which requires an annotated constructor?

Copy link
Member Author

@dlvenable dlvenable Nov 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if there are two separate single argument constructors?

Because the @DataPrepperPlugin allows configuring the pluginConfigurationType, the single argument constructor will only match that type. So it should only ever be what is defined by pluginConfigurationType.

What is your vision for v2.0? Do you plan on supporting all of these scenarios in the future as well? Would it be possible to move to an explicit model which requires an annotated constructor?

For 2.0, I'd like to remove the single argument constructor. Thus, the constructor must be either annotated with @DataPrepperConstructor or be the default constructor.
We need to keep at least support for a single argument PluginSetting in 1.x since that is the current behavior. I could change this so that the only implicit single argument constructor is PluginSetting and disallow the POJO-based single-argument constructor. Would that help?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmanning09 ,

I'm going to change this behavior so that the only single parameter constructor supported is PluginSetting. Thus, plugins which use a POJO for the configuration must provide the @DataPrepperConstructor annotation. I believe this should be simpler and is one fewer behavior to deprecate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a commit which, among other changes, now only permits a single-parameter constructor for PluginSetting. To use the custom POJO configuration, plugin authors must add the @DataPrepperPluginConstructor annotation.

* This exists so that this class can still exhibit the correct behavior when creating new plugin
* classes. This whole class is going to be deleted in the next major version, as will this method.
*/
public static void dangerousMethod_setPluginFunction(final BiFunction<PluginSetting, Class<?>, Object> newPluginFunction) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with this as well because of the reasons you provided. Thanks!

final String sslKeyFile,
final String sslKeyPassword) {
private int port = DEFAULT_PORT;
@JsonProperty("request_timeout")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we use REQUEST_TIMEOUT constant here?

static final String REQUEST_TIMEOUT = "request_timeout";
static final String THREAD_COUNT = "thread_count";
static final String MAX_CONNECTION_COUNT = "max_connection_count";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can we remove this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This string is no longer being used because the tests use a POJO. I pushed a change which added the string literal into the @JsonProperty. For those properties which are being used elsewhere, I kept those as constants.

@JsonProperty("request_timeout")
private int requestTimeoutInMillis = DEFAULT_REQUEST_TIMEOUT_MS;
private int threadCount = DEFAULT_THREAD_COUNT;
private int maxConnectionCount = DEFAULT_MAX_CONNECTION_COUNT;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no jsonProperty annotation on top of this variable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default conversion will support snake case, so this should serialize. But, I can add add it to be somewhat more thorough and consistent.

private int requestTimeoutInMillis = DEFAULT_REQUEST_TIMEOUT_MS;
private int threadCount = DEFAULT_THREAD_COUNT;
private int maxConnectionCount = DEFAULT_MAX_CONNECTION_COUNT;
private int maxPendingRequests = DEFAULT_MAX_PENDING_REQUESTS;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@graytaylor0 graytaylor0 mentioned this pull request Nov 1, 2021
5 tasks
…Constructor annotation. This is the preferred constructor. If no other constructor is available for a plugin, use the no-op constructor. Updated the HTTPSource plugin to use this capability to receive both a configuration model and PluginMetrics via the constructor.

Signed-off-by: David Venable <[email protected]>
…g in a single parameter, un-annotated constructor. Cleaned up the HTTPSourceConfig. Other small changes.

Signed-off-by: David Venable <[email protected]>
@dlvenable dlvenable force-pushed the plugin-constructor-injection branch from b53c5dc to d35b3b7 Compare November 1, 2021 22:42
private String sslCertificateFile;

@JsonProperty(SSL_KEY_FILE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between upper case and lower case JsonProperties? I am wondering why ssl_key_password and SSL_KEY_FILE are different.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSL_KEY_FILE is a static final field whereas "ssl_key_password" is a string literal. The former is used in some error strings so I kept it as constant. We could make them all constants if we wanted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I missed the quotes. For a second I thought the properties were defined in a different format. I'd prefer consistency but will defer to your preference here. I don't want to hold this up.

@dlvenable
Copy link
Member Author

I also had left some commented out code in place. I just pushed a commit which cleaned that up.

private String sslCertificateFile;

@JsonProperty(SSL_KEY_FILE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I missed the quotes. For a second I thought the properties were defined in a different format. I'd prefer consistency but will defer to your preference here. I don't want to hold this up.

@dlvenable dlvenable merged commit 3c7ceec into opensearch-project:main Nov 2, 2021
graytaylor0 referenced this pull request in graytaylor0/data-prepper Nov 5, 2021
Support more constructors for plugins by adding the DataPrepperPluginConstructor annotation. This is the preferred constructor. If no other constructor is available for a plugin, use the no-op constructor. Updated the HTTPSource plugin to use this capability to receive both a configuration model and PluginMetrics via the constructor. For a single parameter, un-annotated constructor in plugins, the only supported parameter is once again PluginSetting.

Signed-off-by: David Venable <[email protected]>
@dlvenable dlvenable deleted the plugin-constructor-injection branch November 29, 2021 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants