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

org.eclipse.jetty.deploy.providers.ScanningAppProvider Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.deploy.providers;

import java.io.FilenameFilter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Environment;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ManagedObject("Abstract Provider for loading webapps")
public abstract class ScanningAppProvider extends ContainerLifeCycle implements AppProvider
{
    private static final Logger LOG = LoggerFactory.getLogger(ScanningAppProvider.class);

    private final Map _appMap = new HashMap<>();

    private DeploymentManager _deploymentManager;
    private FilenameFilter _filenameFilter;
    private final List _monitored = new CopyOnWriteArrayList<>();
    private int _scanInterval = 10;
    private Scanner _scanner;
    private boolean _useRealPaths;
    private String _environmentName;
    private boolean _deferInitialScan = false;

    private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener()
    {
        @Override
        public void pathAdded(Path path) throws Exception
        {
            ScanningAppProvider.this.pathAdded(path);
        }

        @Override
        public void pathChanged(Path path) throws Exception
        {
            ScanningAppProvider.this.pathChanged(path);
        }

        @Override
        public void pathRemoved(Path path) throws Exception
        {
            ScanningAppProvider.this.pathRemoved(path);
        }
    };

    protected ScanningAppProvider()
    {
        this(null);
    }

    protected ScanningAppProvider(FilenameFilter filter)
    {
        _filenameFilter = filter;
        installBean(_appMap);
    }

    @Override
    public String getEnvironmentName()
    {
        return _environmentName;
    }

    public void setEnvironmentName(String environmentName)
    {
        _environmentName = environmentName;
    }

    /**
     * @return True if the real path of the scanned files should be used for deployment.
     */
    public boolean isUseRealPaths()
    {
        return _useRealPaths;
    }

    /**
     * @param useRealPaths True if the real path of the scanned files should be used for deployment.
     */
    public void setUseRealPaths(boolean useRealPaths)
    {
        _useRealPaths = useRealPaths;
    }

    protected void setFilenameFilter(FilenameFilter filter)
    {
        if (isRunning())
            throw new IllegalStateException();
        _filenameFilter = filter;
    }

    /**
     * @return The index of currently deployed applications.
     */
    protected Map getDeployedApps()
    {
        return _appMap;
    }

    /**
     * Called by the Scanner.DiscreteListener to create a new App object.
     * Isolated in a method so that it is possible to override the default App
     * object for specialized implementations of the AppProvider.
     *
     * @param path The file that is the context.xml. It is resolved by
     * {@link org.eclipse.jetty.util.resource.ResourceFactory#newResource(String)}
     * @return The App object for this particular context definition file.
     */
    protected App createApp(Path path)
    {
        App app = new App(_deploymentManager, this, path);
        if (LOG.isDebugEnabled())
            LOG.debug("{} creating {}", this, app);

        String defaultEnvironmentName = _deploymentManager.getDefaultEnvironmentName();

        String environmentName = app.getEnvironmentName();
        if (StringUtil.isBlank(environmentName) && StringUtil.isNotBlank(defaultEnvironmentName))
        {
            environmentName = defaultEnvironmentName;
            app.getProperties().put(Deployable.ENVIRONMENT, environmentName);
            if (LOG.isDebugEnabled())
                LOG.debug("{} default environment for {}", this, app);
        }

        if (StringUtil.isNotBlank(environmentName))
        {
            // If the app specifies the environment for this provider, then this deployer will deploy it.
            if (environmentName.equalsIgnoreCase(getEnvironmentName()))
            {
                if (LOG.isDebugEnabled())
                    LOG.debug("{} created {}", this, app);
                return app;
            }

            // If we are the default provider then we may warn
            if (getEnvironmentName().equalsIgnoreCase(defaultEnvironmentName))
            {
                // if the app specified an environment name, then produce warning if there is no provider for it.
                if (!_deploymentManager.hasAppProviderFor(environmentName))
                    LOG.warn("No AppProvider with environment {} for {}", environmentName, app);
                return null;
            }
        }

        LOG.warn("{} no environment for {}, ignoring", this, app);
        return null;
    }

    @Override
    protected void doStart() throws Exception
    {
        if (LOG.isDebugEnabled())
            LOG.debug("{}.doStart()", this.getClass().getSimpleName());
        if (_monitored.size() == 0)
            throw new IllegalStateException("No configuration dir specified");
        if (_environmentName == null)
        {
            List nonCore = Environment.getAll().stream().filter(environment -> !environment.equals(Environment.CORE)).toList();
            if (nonCore.size() != 1)
                throw new IllegalStateException("No environment configured");
            _environmentName = nonCore.get(0).getName();
        }

        Environment environment = Environment.get(_environmentName);
        if (environment == null)
            throw new IllegalStateException("Unknown environment " + _environmentName);

        LOG.info("Deployment monitor {} in {} at intervals {}s", getEnvironmentName(), _monitored, getScanInterval());
        List files = new ArrayList<>();
        for (Resource resource : _monitored)
        {
            if (Resources.missing(resource))
            {
                LOG.warn("Does not exist: {}", resource);
                continue; // skip
            }

            // handle resource smartly
            for (Resource r: resource)
            {
                Path path = r.getPath();
                if (path == null)
                {
                    LOG.warn("Not based on FileSystem Path: {}", r);
                    continue; // skip
                }
                if (Files.isDirectory(path) || Files.isReadable(path))
                    files.add(resource.getPath());
                else
                    LOG.warn("Unsupported Path (not a directory and/or not readable): {}", r);
            }
        }

        _scanner = new Scanner(null, _useRealPaths);
        _scanner.setScanDirs(files);
        _scanner.setScanInterval(_scanInterval);
        _scanner.setFilenameFilter(_filenameFilter);
        _scanner.setReportDirs(true);
        _scanner.setScanDepth(1); //consider direct dir children of monitored dir
        _scanner.addListener(_scannerListener);
        _scanner.setReportExistingFilesOnStartup(true);
        _scanner.setAutoStartScanning(!_deferInitialScan);
        addBean(_scanner);

        if (isDeferInitialScan())
        {
            // Setup listener to wait for Server in STARTED state, which
            // triggers the first scan of the monitored directories
            getDeploymentManager().getServer().addEventListener(
                new LifeCycle.Listener()
                {
                    @Override
                    public void lifeCycleStarted(LifeCycle event)
                    {
                        if (event instanceof Server)
                        {
                            if (LOG.isDebugEnabled())
                                LOG.debug("Triggering Deferred Scan of {}", _monitored);
                            _scanner.startScanning();
                        }
                    }
                });
        }

        super.doStart();
    }

    @Override
    protected void doStop() throws Exception
    {
        super.doStop();
        if (_scanner != null)
        {
            removeBean(_scanner);
            _scanner.removeListener(_scannerListener);
            _scanner = null;
        }
    }

    protected boolean exists(String path)
    {
        return _scanner.exists(path);
    }

    protected void pathAdded(Path path) throws Exception
    {
        App app = ScanningAppProvider.this.createApp(path);
        if (LOG.isDebugEnabled())
            LOG.debug("fileAdded {}: {}", path, app);

        if (app != null)
        {
            _appMap.put(path, app);
            _deploymentManager.addApp(app);
        }
    }

    protected void pathChanged(Path path) throws Exception
    {
        App oldApp = _appMap.remove(path);
        if (oldApp != null)
            _deploymentManager.removeApp(oldApp);
        App app = ScanningAppProvider.this.createApp(path);
        if (LOG.isDebugEnabled())
            LOG.debug("fileChanged {}: {}", path, app);
        if (app != null)
        {
            _appMap.put(path, app);
            _deploymentManager.addApp(app);
        }
    }

    protected void pathRemoved(Path path) throws Exception
    {
        App app = _appMap.remove(path);
        if (LOG.isDebugEnabled())
            LOG.debug("fileRemoved {}: {}", path, app);
        if (app != null)
            _deploymentManager.removeApp(app);
    }

    /**
     * Get the deploymentManager.
     *
     * @return the deploymentManager
     */
    public DeploymentManager getDeploymentManager()
    {
        return _deploymentManager;
    }

    public Resource getMonitoredDirResource()
    {
        if (_monitored.size() == 0)
            return null;
        if (_monitored.size() > 1)
            throw new IllegalStateException();
        return _monitored.get(0);
    }

    public String getMonitoredDirName()
    {
        Resource resource = getMonitoredDirResource();
        return resource == null ? null : resource.toString();
    }

    @ManagedAttribute("scanning interval to detect changes which need reloaded")
    public int getScanInterval()
    {
        return _scanInterval;
    }

    @Override
    public void setDeploymentManager(DeploymentManager deploymentManager)
    {
        _deploymentManager = deploymentManager;
    }

    public void setMonitoredResources(List resources)
    {
        _monitored.clear();
        if (resources == null)
            return;
        resources.stream().filter(Objects::nonNull).forEach(_monitored::add);
    }

    public List getMonitoredResources()
    {
        return Collections.unmodifiableList(_monitored);
    }

    public void setMonitoredDirResource(Resource resource)
    {
        setMonitoredResources(Collections.singletonList(resource));
    }

    public void addScannerListener(Scanner.Listener listener)
    {
        _scanner.addListener(listener);
    }

    /**
     * @param dir Directory to scan for context descriptors or war files
     */
    public void setMonitoredDirName(String dir)
    {
        setMonitoredDirectories(Collections.singletonList(dir));
    }

    public void setMonitoredDirectories(Collection directories)
    {
        try
        {
            List resources = new ArrayList<>();
            for (String dir : directories)
            {
                resources.add(ResourceFactory.of(this).newResource(dir));
            }
            setMonitoredResources(resources);
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Test if initial scan should be deferred.
     *
     * @return true if initial scan is deferred, false to have initial scan occur on startup of ScanningAppProvider.
     */
    public boolean isDeferInitialScan()
    {
        return _deferInitialScan;
    }

    /**
     * Flag to control initial scan behavior.
     *
     * 
    *
  • {@code true} - to have initial scan deferred until the {@link Server} component * has reached it's STARTED state.
    * Note: any failures in a deploy will not fail the Server startup in this mode.
  • *
  • {@code false} - (default value) to have initial scan occur as normal on * ScanningAppProvider startup.
  • *
* * @param defer true to defer initial scan, false to have initial scan occur on startup of ScanningAppProvider. */ public void setDeferInitialScan(boolean defer) { _deferInitialScan = defer; } public void setScanInterval(int scanInterval) { _scanInterval = scanInterval; } @ManagedOperation(value = "Scan the monitored directories", impact = "ACTION") public void scan() { LOG.info("Performing scan of monitored directories: {}", getMonitoredResources().stream().map((r) -> r.getURI().toASCIIString()) .collect(Collectors.joining(", ", "[", "]")) ); _scanner.nudge(); } @Override public String toString() { return String.format("%s@%x%s", this.getClass(), hashCode(), _monitored); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy