blob: 0fe05380a529fc123f1eb2e96e967abb9b11d338 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.registry.web.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
import org.apache.nifi.registry.client.NiFiRegistryClientConfig;
import org.apache.nifi.registry.db.DatabaseProfileValueSource;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
import javax.annotation.PostConstruct;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A base class to simplify creating integration tests against an API application running with an embedded server and volatile DB.
*/
@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class)
public abstract class IntegrationTestBase {
private static final String CONTEXT_PATH = "/nifi-registry-api";
@TestConfiguration
public static class TestConfigurationClass {
/* REQUIRED: Any subclass extending IntegrationTestBase must add a Spring profile that defines a
* property value for this key containing the path to the nifi-registy.properties file to use to
* create a NiFiRegistryProperties Bean in the ApplicationContext. */
@Value("${nifi.registry.properties.file}")
private String propertiesFileLocation;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private NiFiRegistryProperties testProperties;
@Bean
public JettyServletWebServerFactory jettyEmbeddedServletContainerFactory() {
JettyServletWebServerFactory jettyContainerFactory = new JettyServletWebServerFactory();
jettyContainerFactory.setContextPath(CONTEXT_PATH);
return jettyContainerFactory;
}
@Bean
public NiFiRegistryProperties getNiFiRegistryProperties() {
readLock.lock();
try {
if (testProperties == null) {
testProperties = loadNiFiRegistryProperties(propertiesFileLocation);
}
} finally {
readLock.unlock();
}
return testProperties;
}
}
@Autowired
private NiFiRegistryProperties properties;
/* OPTIONAL: Any subclass that extends this base class MAY provide or specify a @TestConfiguration that provides a
* NiFiRegistryClientConfig @Bean. The properties specified should correspond with the integration test cases in
* the concrete subclass. See SecureFileIT for an example. */
@Autowired(required = false)
private NiFiRegistryClientConfig clientConfig;
/* This will be injected with the random port assigned to the embedded Jetty container. */
@LocalServerPort
private int port;
/**
* Subclasses can access this auto-configured JAX-RS client to communicate to the NiFi Registry Server
*/
protected Client client;
@PostConstruct
void initialize() {
if (this.clientConfig != null) {
this.client = createClientFromConfig(this.clientConfig);
} else {
this.client = ClientBuilder.newClient();
}
}
/**
* Subclasses can utilize this method to build a URL that has the correct protocol, hostname, and port
* for a given path.
*
* @param relativeResourcePath the path component of the resource you wish to access, relative to the
* base API URL, where the base includes the servlet context path.
*
* @return a String containing the absolute URL of the resource.
*/
String createURL(String relativeResourcePath) {
if (relativeResourcePath == null) {
throw new IllegalArgumentException("Resource path cannot be null");
}
final StringBuilder baseUriBuilder = new StringBuilder(createBaseURL()).append(CONTEXT_PATH);
if (!relativeResourcePath.startsWith("/")) {
baseUriBuilder.append('/');
}
baseUriBuilder.append(relativeResourcePath);
return baseUriBuilder.toString();
}
/**
* Sub-classes can utilize this method to obtain the base-url for a client.
*
* @return a string containing the base url which includes the scheme, host, and port
*/
String createBaseURL() {
final boolean isSecure = this.properties.getSslPort() != null;
final String protocolSchema = isSecure ? "https" : "http";
final StringBuilder baseUriBuilder = new StringBuilder()
.append(protocolSchema).append("://localhost:").append(port);
return baseUriBuilder.toString();
}
NiFiRegistryClientConfig createClientConfig(String baseUrl) {
final NiFiRegistryClientConfig.Builder builder = new NiFiRegistryClientConfig.Builder();
builder.baseUrl(baseUrl);
if (this.clientConfig != null) {
if (this.clientConfig.getSslContext() != null) {
builder.sslContext(this.clientConfig.getSslContext());
}
if (this.clientConfig.getHostnameVerifier() != null) {
builder.hostnameVerifier(this.clientConfig.getHostnameVerifier());
}
}
return builder.build();
}
/**
* A helper method for loading NiFiRegistryProperties by reading *.properties files from disk.
*
* @param propertiesFilePath The location of the properties file
* @return A NiFIRegistryProperties instance based on the properties file contents
*/
static NiFiRegistryProperties loadNiFiRegistryProperties(String propertiesFilePath) {
NiFiRegistryProperties properties = new NiFiRegistryProperties();
try (final FileReader reader = new FileReader(propertiesFilePath)) {
properties.load(reader);
} catch (final IOException ioe) {
throw new RuntimeException("Unable to load properties: " + ioe, ioe);
}
return properties;
}
private static Client createClientFromConfig(NiFiRegistryClientConfig registryClientConfig) {
final ClientConfig clientConfig = new ClientConfig();
clientConfig.register(jacksonJaxbJsonProvider());
final ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig);
final SSLContext sslContext = registryClientConfig.getSslContext();
if (sslContext != null) {
clientBuilder.sslContext(sslContext);
}
final HostnameVerifier hostnameVerifier = registryClientConfig.getHostnameVerifier();
if (hostnameVerifier != null) {
clientBuilder.hostnameVerifier(hostnameVerifier);
}
return clientBuilder.build();
}
private static JacksonJaxbJsonProvider jacksonJaxbJsonProvider() {
JacksonJaxbJsonProvider jacksonJaxbJsonProvider = new JacksonJaxbJsonProvider();
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));
mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector(mapper.getTypeFactory()));
// Ignore unknown properties so that deployed client remain compatible with future versions of NiFi Registry that add new fields
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jacksonJaxbJsonProvider.setMapper(mapper);
return jacksonJaxbJsonProvider;
}
}