/*
* 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.reporting;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.events.EventReporter;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.remote.client.SiteToSiteClient;
import org.apache.nifi.ssl.SSLContextService;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Base class for ReportingTasks that send data over site-to-site.
*/
public abstract class AbstractSiteToSiteReportingTask extends AbstractReportingTask {
protected static final String DESTINATION_URL_PATH = "/nifi";
static final PropertyDescriptor DESTINATION_URL = new PropertyDescriptor.Builder()
.name("Destination URL")
.description("The URL of the destination NiFi instance to send data to, " +
"should be in the format http(s)://host:port/nifi.")
.required(true)
.expressionLanguageSupported(true)
.addValidator(new NiFiUrlValidator())
.build();
static final PropertyDescriptor PORT_NAME = new PropertyDescriptor.Builder()
.name("Input Port Name")
.description("The name of the Input Port to deliver data to.")
.required(true)
.expressionLanguageSupported(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
static final PropertyDescriptor SSL_CONTEXT = new PropertyDescriptor.Builder()
.name("SSL Context Service")
.description("The SSL Context Service to use when communicating with the destination. If not specified, communications will not be secure.")
.required(false)
.identifiesControllerService(SSLContextService.class)
.build();
static final PropertyDescriptor INSTANCE_URL = new PropertyDescriptor.Builder()
.name("Instance URL")
.description("The URL of this instance to use in the Content URI of each event.")
.required(true)
.expressionLanguageSupported(true)
.defaultValue("http://${hostname(true)}:8080/nifi")
.addValidator(new NiFiUrlValidator())
.build();
static final PropertyDescriptor COMPRESS = new PropertyDescriptor.Builder()
.name("Compress Events")
.description("Indicates whether or not to compress the data being sent.")
.required(true)
.allowableValues("true", "false")
.defaultValue("true")
.build();
static final PropertyDescriptor TIMEOUT = new PropertyDescriptor.Builder()
.name("Communications Timeout")
.description("Specifies how long to wait to a response from the destination before deciding that an error has occurred and canceling the transaction")
.required(true)
.defaultValue("30 secs")
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR)
.build();
static final PropertyDescriptor BATCH_SIZE = new PropertyDescriptor.Builder()
.name("Batch Size")
.description("Specifies how many records to send in a single batch, at most.")
.required(true)
.defaultValue("1000")
.addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
.build();
protected volatile SiteToSiteClient siteToSiteClient;
@Override
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
final List<PropertyDescriptor> properties = new ArrayList<>();
properties.add(DESTINATION_URL);
properties.add(PORT_NAME);
properties.add(SSL_CONTEXT);
properties.add(INSTANCE_URL);
properties.add(COMPRESS);
properties.add(TIMEOUT);
properties.add(BATCH_SIZE);
return properties;
}
@OnScheduled
public void setup(final ConfigurationContext context) throws IOException {
final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT).asControllerService(SSLContextService.class);
final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
final ComponentLog logger = getLogger();
final EventReporter eventReporter = new EventReporter() {
@Override
public void reportEvent(final Severity severity, final String category, final String message) {
switch (severity) {
case WARNING:
logger.warn(message);
break;
case ERROR:
logger.error(message);
break;
default:
break;
}
}
};
final String destinationUrl = context.getProperty(DESTINATION_URL).evaluateAttributeExpressions().getValue();
siteToSiteClient = new SiteToSiteClient.Builder()
.url(destinationUrl)
.portName(context.getProperty(PORT_NAME).getValue())
.useCompression(context.getProperty(COMPRESS).asBoolean())
.eventReporter(eventReporter)
.sslContext(sslContext)
.timeout(context.getProperty(TIMEOUT).asTimePeriod(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
.build();
}
@OnStopped
public void shutdown() throws IOException {
final SiteToSiteClient client = getClient();
if (client != null) {
client.close();
}
}
// this getter is intended explicitly for testing purposes
protected SiteToSiteClient getClient() {
return this.siteToSiteClient;
}
static class NiFiUrlValidator implements Validator {
@Override
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
final String value = context.newPropertyValue(input).evaluateAttributeExpressions().getValue();
URL url;
try {
url = new URL(value);
} catch (final Exception e) {
return new ValidationResult.Builder()
.input(input)
.subject(subject)
.valid(false)
.explanation("Not a valid URL")
.build();
}
if (url != null && !url.getPath().equals(DESTINATION_URL_PATH)) {
return new ValidationResult.Builder()
.input(input)
.subject(subject)
.valid(false)
.explanation("URL path must be " + DESTINATION_URL_PATH)
.build();
}
return new ValidationResult.Builder()
.input(input)
.subject(subject)
.valid(true)
.build();
}
}
}