All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.nifi.registry.jetty.handler.StandardHandlerProvider Maven / Gradle / Ivy

The newest version!
/*
 * 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.jetty.handler;

import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.eclipse.jetty.ee10.webapp.MetaInfConfiguration;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee10.webapp.WebAppClassLoader;
import org.eclipse.jetty.ee10.webapp.WebAppContext;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * Standard Jetty Handler Provider responsible for loading web applications
 */
public class StandardHandlerProvider implements HandlerProvider {
    private static final int MAX_FORM_CONTENT_SIZE = 600000;

    private static final String UI_CONTEXT_PATH = "/nifi-registry";

    private static final Pattern UI_FILE_PATTERN = Pattern.compile("^nifi-registry-web-ui-.+?\\.war$");

    private static final String API_CONTEXT_PATH = "/nifi-registry-api";

    private static final Pattern API_FILE_PATTERN = Pattern.compile("^nifi-registry-web-api-.+?\\.war$");

    private static final String DOCS_CONTEXT_PATH = "/nifi-registry-docs";

    private static final Pattern DOCS_FILE_PATTERN = Pattern.compile("^nifi-registry-web-docs-.+?\\.war$");

    private static final String HTML_DOCS_PATH = "/html/*";

    private static final String HTML_DOCS_RELATIVE_DIRECTORY = "html";

    private static final String REST_API_DOCS_PATH = "/rest-api/*";

    private static final String REST_API_DOCS_RELATIVE_PATH = "webapp/docs/rest-api";

    private static final String OIDC_SUPPORTED_PARAMETER = "oidc-supported";

    private static final String PROPERTIES_PARAMETER = "nifi-registry.properties";

    private static final String RESOURCE_BASE_PARAMETER = "baseResource";

    private static final String DIR_ALLOWED_PARAMETER = "dirAllowed";

    private static final String WEB_INF_JAR_PATTERN_ATTRIBUTE = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern";

    private static final String WEB_INF_JAR_PATTERN = ".*/spring-[^/]*\\.jar$";

    private static final String CONTAINER_JAR_PATTERN = ".*/jetty-jakarta-servlet-api-[^/]*\\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\\.jar";

    private final String docsDirectory;

    public StandardHandlerProvider(final String docsDirectory) {
        this.docsDirectory = docsDirectory;
    }

    /**
     * Get Jetty Handler for Registry Server containing mappings to web applications
     *
     * @param properties Registry properties
     * @return Jetty Handler
     */
    @Override
    public Handler getHandler(final NiFiRegistryProperties properties) {
        Objects.requireNonNull(properties, "Properties required");

        final File libDirectory = properties.getWarLibDirectory();
        final File workDirectory = properties.getWebWorkingDirectory();

        final Handler.Collection handlers = new ContextHandlerCollection();
        // Add Header Writer Handler before others
        handlers.addHandler(new HeaderWriterHandler());

        final WebAppContext userInterfaceContext = getWebAppContext(libDirectory, workDirectory, ClassLoader.getSystemClassLoader(), UI_FILE_PATTERN, UI_CONTEXT_PATH);
        userInterfaceContext.setInitParameter(OIDC_SUPPORTED_PARAMETER, Boolean.toString(properties.isOidcEnabled()));
        handlers.addHandler(userInterfaceContext);

        final ClassLoader apiClassLoader = getApiClassLoader(properties.getDatabaseDriverDirectory());
        final WebAppContext apiContext = getWebAppContext(libDirectory, workDirectory, apiClassLoader, API_FILE_PATTERN, API_CONTEXT_PATH);
        apiContext.setAttribute(PROPERTIES_PARAMETER, properties);
        handlers.addHandler(apiContext);

        final WebAppContext docsContext = getWebAppContext(libDirectory, workDirectory, ClassLoader.getSystemClassLoader(), DOCS_FILE_PATTERN, DOCS_CONTEXT_PATH);
        final Path docsDir = getDocsDirectory();
        final ServletHolder docsServletHolder = getDocsServletHolder(docsDir);
        docsContext.addServlet(docsServletHolder, HTML_DOCS_PATH);

        final Path apiDocsDir = getApiDocsDirectory(apiContext);
        final ServletHolder apiDocsServletHolder = getDocsServletHolder(apiDocsDir);
        docsContext.addServlet(apiDocsServletHolder, REST_API_DOCS_PATH);

        handlers.addHandler(docsContext);

        return handlers;
    }

    private ClassLoader getApiClassLoader(final String databaseDriverDirectory) {
        final URL[] resourceLocations = getResourceLocations(databaseDriverDirectory);
        final ClassLoader apiClassLoader;
        if (resourceLocations.length == 0) {
            apiClassLoader = ClassLoader.getSystemClassLoader();
        } else {
            apiClassLoader = new URLClassLoader(resourceLocations, ClassLoader.getSystemClassLoader());
        }
        return apiClassLoader;
    }

    private WebAppContext getWebAppContext(
            final File libDirectory,
            final File workDirectory,
            final ClassLoader parentClassLoader,
            final Pattern applicationFilePattern,
            final String contextPath
    ) {
        final File applicationFile = getApplicationFile(libDirectory, applicationFilePattern);
        final WebAppContext webAppContext = new WebAppContext(applicationFile.getPath(), contextPath);
        webAppContext.setContextPath(contextPath);
        webAppContext.setMaxFormContentSize(MAX_FORM_CONTENT_SIZE);
        webAppContext.setAttribute(WEB_INF_JAR_PATTERN_ATTRIBUTE, WEB_INF_JAR_PATTERN);
        webAppContext.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, CONTAINER_JAR_PATTERN);
        webAppContext.setErrorHandler(getErrorHandler());

        final File tempDirectory = getTempDirectory(workDirectory, applicationFile.getName());
        webAppContext.setTempDirectory(tempDirectory);

        final WebAppClassLoader webAppClassLoader = new WebAppClassLoader(parentClassLoader, webAppContext);
        webAppContext.setClassLoader(webAppClassLoader);

        return webAppContext;
    }

    private File getApplicationFile(final File directory, final Pattern filenamePattern) {
        final File[] applicationFiles = directory.listFiles((file, filename) -> filenamePattern.matcher(filename).matches());
        if (applicationFiles == null || applicationFiles.length == 0) {
            throw new IllegalStateException(String.format("Required Application matching [%s] not found in directory [%s]", filenamePattern, directory));
        }
        return applicationFiles[0];
    }

    private ErrorPageErrorHandler getErrorHandler() {
        final ErrorPageErrorHandler errorHandler = new ErrorPageErrorHandler();
        errorHandler.setShowServlet(false);
        errorHandler.setShowStacks(false);
        errorHandler.setShowMessageInTitle(false);
        return errorHandler;
    }

    private File getTempDirectory(final File webWorkingDirectory, final String filename) {
        final File tempDirectory = new File(webWorkingDirectory, filename);
        if (tempDirectory.isDirectory()) {
            if (tempDirectory.canWrite()) {
                return tempDirectory;
            } else {
                throw new IllegalStateException(String.format("Temporary Directory [%s] not writable", tempDirectory));
            }
        } else {
            if (tempDirectory.mkdirs()) {
                return tempDirectory;
            } else {
                throw new IllegalStateException(String.format("Temporary Directory [%s] creation failed", tempDirectory));
            }
        }
    }

    private URL[] getResourceLocations(final String databaseDriverDirectory) {
        final URL[] resourceLocations;

        if (StringUtils.isBlank(databaseDriverDirectory)) {
            resourceLocations = new URL[0];
        } else {
            final File driverDirectory = new File(databaseDriverDirectory);
            if (driverDirectory.canRead()) {
                final List locations = new ArrayList<>();
                final URL driverDirectoryUrl = getUrl(driverDirectory);
                locations.add(driverDirectoryUrl);

                final File[] files = driverDirectory.listFiles();
                if (files != null) {
                    Arrays.stream(files)
                            .filter(File::isFile)
                            .map(this::getUrl)
                            .forEach(locations::add);
                }
                resourceLocations = locations.toArray(new URL[0]);
            } else {
                resourceLocations = new URL[0];
            }
        }
        return resourceLocations;
    }

    private URL getUrl(final File file) {
        try {
            return file.toURI().toURL();
        } catch (final MalformedURLException e) {
            throw new IllegalStateException(String.format("File URL [%s] conversion failed", file), e);
        }
    }

    private Path getDocsDirectory() {
        final Path docsDirectoryPath = Paths.get(docsDirectory);
        Path docsDir;
        try {
            docsDir = docsDirectoryPath.toRealPath();
        } catch (IOException e) {
            docsDir = docsDirectoryPath;
            if (!docsDir.toFile().mkdirs()) {
                final String message = String.format("Documentation Directory [%s] creation failed", docsDirectory);
                throw new IllegalStateException(message);
            }
        }
        return docsDir.resolve(HTML_DOCS_RELATIVE_DIRECTORY);
    }

    private ServletHolder getDocsServletHolder(final Path directory) {
        final ServletHolder servletHolder = new ServletHolder(directory.getFileName().toString(), DefaultServlet.class);
        servletHolder.setInitParameter(RESOURCE_BASE_PARAMETER, directory.toString());
        servletHolder.setInitParameter(DIR_ALLOWED_PARAMETER, Boolean.FALSE.toString());
        return servletHolder;
    }

    private Path getApiDocsDirectory(final WebAppContext apiContext) {
        final File apiDocsDir = new File(apiContext.getTempDirectory(), REST_API_DOCS_RELATIVE_PATH);
        if (apiDocsDir.canRead() || apiDocsDir.mkdirs()) {
            return apiDocsDir.toPath();
        }
        throw new IllegalStateException(String.format("REST API Documentation Directory [%s] not readable", apiDocsDir.getAbsolutePath()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy