Java tutorial
/* * 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.minifi.bootstrap.configuration.ingestors; import org.apache.commons.io.input.TeeInputStream; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.nifi.minifi.bootstrap.ConfigurationFileHolder; import org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeNotifier; import org.apache.nifi.minifi.bootstrap.configuration.ListenerHandleResult; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator; import org.apache.nifi.minifi.bootstrap.configuration.differentiators.interfaces.Differentiator; import org.apache.nifi.minifi.bootstrap.configuration.ingestors.interfaces.ChangeIngestor; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.URI; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.function.Supplier; import static org.apache.nifi.minifi.bootstrap.configuration.ConfigurationChangeCoordinator.NOTIFIER_INGESTORS_KEY; import static org.apache.nifi.minifi.bootstrap.configuration.differentiators.WholeConfigDifferentiator.WHOLE_CONFIG_KEY; public class RestChangeIngestor implements ChangeIngestor { private static final Map<String, Supplier<Differentiator<InputStream>>> DIFFERENTIATOR_CONSTRUCTOR_MAP; static { HashMap<String, Supplier<Differentiator<InputStream>>> tempMap = new HashMap<>(); tempMap.put(WHOLE_CONFIG_KEY, WholeConfigDifferentiator::getInputStreamDifferentiator); DIFFERENTIATOR_CONSTRUCTOR_MAP = Collections.unmodifiableMap(tempMap); } public static final String GET_TEXT = "This is a config change listener for an Apache NiFi - MiNiFi instance.\n" + "Use this rest server to upload a conf.yml to configure the MiNiFi instance.\n" + "Send a POST http request to '/' to upload the file."; public static final String OTHER_TEXT = "This is not a support HTTP operation. Please use GET to get more information or POST to upload a new config.yml file.\n"; public static final String POST = "POST"; public static final String GET = "GET"; private final static Logger logger = LoggerFactory.getLogger(RestChangeIngestor.class); private static final String RECEIVE_HTTP_BASE_KEY = NOTIFIER_INGESTORS_KEY + ".receive.http"; public static final String PORT_KEY = RECEIVE_HTTP_BASE_KEY + ".port"; public static final String HOST_KEY = RECEIVE_HTTP_BASE_KEY + ".host"; public static final String TRUSTSTORE_LOCATION_KEY = RECEIVE_HTTP_BASE_KEY + ".truststore.location"; public static final String TRUSTSTORE_PASSWORD_KEY = RECEIVE_HTTP_BASE_KEY + ".truststore.password"; public static final String TRUSTSTORE_TYPE_KEY = RECEIVE_HTTP_BASE_KEY + ".truststore.type"; public static final String KEYSTORE_LOCATION_KEY = RECEIVE_HTTP_BASE_KEY + ".keystore.location"; public static final String KEYSTORE_PASSWORD_KEY = RECEIVE_HTTP_BASE_KEY + ".keystore.password"; public static final String KEYSTORE_TYPE_KEY = RECEIVE_HTTP_BASE_KEY + ".keystore.type"; public static final String NEED_CLIENT_AUTH_KEY = RECEIVE_HTTP_BASE_KEY + ".need.client.auth"; public static final String DIFFERENTIATOR_KEY = RECEIVE_HTTP_BASE_KEY + ".differentiator"; private final Server jetty; private volatile Differentiator<InputStream> differentiator; private volatile ConfigurationChangeNotifier configurationChangeNotifier; public RestChangeIngestor() { QueuedThreadPool queuedThreadPool = new QueuedThreadPool(); queuedThreadPool.setDaemon(true); jetty = new Server(queuedThreadPool); } @Override public void initialize(Properties properties, ConfigurationFileHolder configurationFileHolder, ConfigurationChangeNotifier configurationChangeNotifier) { logger.info("Initializing"); final String differentiatorName = properties.getProperty(DIFFERENTIATOR_KEY); if (differentiatorName != null && !differentiatorName.isEmpty()) { Supplier<Differentiator<InputStream>> differentiatorSupplier = DIFFERENTIATOR_CONSTRUCTOR_MAP .get(differentiatorName); if (differentiatorSupplier == null) { throw new IllegalArgumentException( "Property, " + DIFFERENTIATOR_KEY + ", has value " + differentiatorName + " which does not " + "correspond to any in the PullHttpChangeIngestor Map:" + DIFFERENTIATOR_CONSTRUCTOR_MAP.keySet()); } differentiator = differentiatorSupplier.get(); } else { differentiator = WholeConfigDifferentiator.getInputStreamDifferentiator(); } differentiator.initialize(properties, configurationFileHolder); // create the secure connector if keystore location is specified if (properties.getProperty(KEYSTORE_LOCATION_KEY) != null) { createSecureConnector(properties); } else { // create the unsecure connector otherwise createConnector(properties); } this.configurationChangeNotifier = configurationChangeNotifier; HandlerCollection handlerCollection = new HandlerCollection(true); handlerCollection.addHandler(new JettyHandler()); jetty.setHandler(handlerCollection); } @Override public void start() { try { jetty.start(); logger.info("RestChangeIngester has started and is listening on port {}.", new Object[] { getPort() }); } catch (Exception e) { throw new IllegalStateException(e); } } @Override public void close() throws IOException { logger.warn("Shutting down the jetty server"); try { jetty.stop(); jetty.destroy(); } catch (Exception e) { throw new IOException(e); } logger.warn("Done shutting down the jetty server"); } public URI getURI() { return jetty.getURI(); } public int getPort() { if (!jetty.isStarted()) { throw new IllegalStateException("Jetty server not started"); } return ((ServerConnector) jetty.getConnectors()[0]).getLocalPort(); } private void createConnector(Properties properties) { final ServerConnector http = new ServerConnector(jetty); http.setPort(Integer.parseInt(properties.getProperty(PORT_KEY, "0"))); http.setHost(properties.getProperty(HOST_KEY, "localhost")); // Severely taxed or distant environments may have significant delays when executing. http.setIdleTimeout(30000L); jetty.addConnector(http); logger.info("Added an http connector on the host '{}' and port '{}'", new Object[] { http.getHost(), http.getPort() }); } private void createSecureConnector(Properties properties) { SslContextFactory ssl = new SslContextFactory(); if (properties.getProperty(KEYSTORE_LOCATION_KEY) != null) { ssl.setKeyStorePath(properties.getProperty(KEYSTORE_LOCATION_KEY)); ssl.setKeyStorePassword(properties.getProperty(KEYSTORE_PASSWORD_KEY)); ssl.setKeyStoreType(properties.getProperty(KEYSTORE_TYPE_KEY)); } if (properties.getProperty(TRUSTSTORE_LOCATION_KEY) != null) { ssl.setTrustStorePath(properties.getProperty(TRUSTSTORE_LOCATION_KEY)); ssl.setTrustStorePassword(properties.getProperty(TRUSTSTORE_PASSWORD_KEY)); ssl.setTrustStoreType(properties.getProperty(TRUSTSTORE_TYPE_KEY)); ssl.setNeedClientAuth(Boolean.parseBoolean(properties.getProperty(NEED_CLIENT_AUTH_KEY, "true"))); } // build the connector final ServerConnector https = new ServerConnector(jetty, ssl); // set host and port https.setPort(Integer.parseInt(properties.getProperty(PORT_KEY, "0"))); https.setHost(properties.getProperty(HOST_KEY, "localhost")); // Severely taxed environments may have significant delays when executing. https.setIdleTimeout(30000L); // add the connector jetty.addConnector(https); logger.info("Added an https connector on the host '{}' and port '{}'", new Object[] { https.getHost(), https.getPort() }); } protected void setDifferentiator(Differentiator<InputStream> differentiator) { this.differentiator = differentiator; } private class JettyHandler extends AbstractHandler { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { logRequest(request); baseRequest.setHandled(true); if (POST.equals(request.getMethod())) { int statusCode; String responseText; try (ByteArrayOutputStream pipedOutputStream = new ByteArrayOutputStream(); TeeInputStream teeInputStream = new TeeInputStream(request.getInputStream(), pipedOutputStream)) { if (differentiator.isNew(teeInputStream)) { // Fill the pipedOutputStream with the rest of the request data while (teeInputStream.available() != 0) { teeInputStream.read(); } ByteBuffer newConfig = ByteBuffer.wrap(pipedOutputStream.toByteArray()); ByteBuffer readOnlyNewConfig = newConfig.asReadOnlyBuffer(); Collection<ListenerHandleResult> listenerHandleResults = configurationChangeNotifier .notifyListeners(readOnlyNewConfig); statusCode = 200; for (ListenerHandleResult result : listenerHandleResults) { if (!result.succeeded()) { statusCode = 500; break; } } responseText = getPostText(listenerHandleResults); } else { statusCode = 409; responseText = "Request received but instance is already running this config."; } writeOutput(response, responseText, statusCode); } } else if (GET.equals(request.getMethod())) { writeOutput(response, GET_TEXT, 200); } else { writeOutput(response, OTHER_TEXT, 404); } } private String getPostText(Collection<ListenerHandleResult> listenerHandleResults) { StringBuilder postResult = new StringBuilder("The result of notifying listeners:\n"); for (ListenerHandleResult result : listenerHandleResults) { postResult.append(result.toString()); postResult.append("\n"); } return postResult.toString(); } private void writeOutput(HttpServletResponse response, String responseText, int responseCode) throws IOException { response.setStatus(responseCode); response.setContentType("text/plain"); response.setContentLength(responseText.length()); try (PrintWriter writer = response.getWriter()) { writer.print(responseText); writer.flush(); } } private void logRequest(HttpServletRequest request) { logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); logger.info("request method = " + request.getMethod()); logger.info("request url = " + request.getRequestURL()); logger.info("context path = " + request.getContextPath()); logger.info("request content type = " + request.getContentType()); logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); } } }