WIP for deployer

This commit is contained in:
Greg Wilkins 2022-05-04 19:41:38 +02:00
parent 3f6f725cce
commit 85af234ed6
9 changed files with 161 additions and 104 deletions

View File

@ -24,7 +24,7 @@ public class App
private final DeploymentManager _manager;
private final AppProvider _provider;
private final String _environment;
private final String _originId;
private final String _filename;
private ContextHandler _context;
/**
@ -33,17 +33,16 @@ public class App
* @param manager the deployment manager
* @param provider the app provider
* @param environment the name of the environment or null for the server environment.
* @param originId the origin ID (The ID that the {@link AppProvider} knows
* about)
* @see App#getOriginId()
* @param filename the filename of the base resource of the application
* @see App#getFilename()
* @see App#getContextPath()
*/
public App(DeploymentManager manager, AppProvider provider, String environment, String originId)
public App(DeploymentManager manager, AppProvider provider, String environment, String filename)
{
_manager = manager;
_provider = provider;
_environment = environment;
_originId = originId;
_filename = filename;
}
/**
@ -128,14 +127,14 @@ public class App
*
* @return String representing the origin of this app.
*/
public String getOriginId()
public String getFilename()
{
return this._originId;
return this._filename;
}
@Override
public String toString()
{
return "App[" + _context + "," + _originId + "]";
return "App[" + _context + "," + _filename + "]";
}
}

View File

@ -140,7 +140,7 @@ public class DeploymentManager extends ContainerLifeCycle
*/
public void addApp(App app)
{
LOG.debug("Deployable added: {}", app.getOriginId());
LOG.debug("Deployable added: {}", app.getFilename());
AppEntry entry = new AppEntry();
entry.app = app;
entry.setLifeCycleNode(_lifecycle.getNodeByName("undeployed"));
@ -293,7 +293,7 @@ public class DeploymentManager extends ContainerLifeCycle
for (AppEntry entry : _apps)
{
if (originId.equals(entry.app.getOriginId()))
if (originId.equals(entry.app.getFilename()))
{
return entry;
}
@ -480,7 +480,7 @@ public class DeploymentManager extends ContainerLifeCycle
*/
public void requestAppGoal(App app, String nodeName)
{
AppEntry appentry = findAppByOriginId(app.getOriginId());
AppEntry appentry = findAppByOriginId(app.getFilename());
if (appentry == null)
{
throw new IllegalStateException("App not being tracked by Deployment Manager: " + app);

View File

@ -79,7 +79,7 @@ public class DeploymentManagerMBean extends ObjectMBean
private String toRef(App app)
{
return String.format("originId=%s,contextPath=%s,appProvider=%s", app.getContextPath(), app.getOriginId(), app.getAppProvider().getClass().getName());
return String.format("originId=%s,contextPath=%s,appProvider=%s", app.getContextPath(), app.getFilename(), app.getAppProvider().getClass().getName());
}
public Collection<ContextHandler> getContexts() throws Exception

View File

@ -15,6 +15,10 @@ package org.eclipse.jetty.deploy.providers;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Locale;
@ -23,11 +27,13 @@ import org.eclipse.jetty.deploy.ConfigurationManager;
import org.eclipse.jetty.deploy.util.FileID;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Environment;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.slf4j.LoggerFactory;
@ -58,11 +64,11 @@ import org.slf4j.LoggerFactory;
* properties for the webapp file as "jetty.webapp" and directory as "jetty.webapps".
*/
@ManagedObject("Provider for start-up deployement of webapps based on presence in directory")
public class WebAppProvider extends ScanningAppProvider
public class ContextProvider extends ScanningAppProvider
{
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(WebAppProvider.class);
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ContextProvider.class);
private boolean _extractWars = false;
private boolean _extract = false;
private boolean _parentLoaderPriority = false;
private ConfigurationManager _configurationManager;
private String _defaultsDescriptor;
@ -115,7 +121,7 @@ public class WebAppProvider extends ScanningAppProvider
}
}
public WebAppProvider()
public ContextProvider()
{
super();
setFilenameFilter(new Filter());
@ -126,21 +132,43 @@ public class WebAppProvider extends ScanningAppProvider
* Get the extractWars.
*
* @return the extractWars
* @deprecated use {@link #isExtract()}
*/
@ManagedAttribute("extract war files")
@Deprecated
public boolean isExtractWars()
{
return _extractWars;
return isExtract();
}
/**
* @return True if WAR and JAR are extraced on deploy.
*/
@ManagedAttribute("extract WAR and JAR files")
public boolean isExtract()
{
return _extract;
}
/**
* Set the extractWars.
*
* @param extractWars the extractWars to set
* @param extract the extractWars to set
* @deprecated use {@link #setExtract(boolean)}
*/
public void setExtractWars(boolean extractWars)
@Deprecated
public void setExtractWars(boolean extract)
{
_extractWars = extractWars;
setExtract(extract);
}
/**
* Set to extract WAR and JAR files.
*
* @param extract the extractWars to set
*/
public void setExtract(boolean extract)
{
_extract = extract;
}
/**
@ -248,6 +276,8 @@ public class WebAppProvider extends ScanningAppProvider
if (_configurationClasses != null)
webapp.setConfigurationClasses(_configurationClasses);
*/
if (_tempDirectory != null)
{
// Since the Temp Dir is really a context base temp directory,
@ -255,9 +285,8 @@ public class WebAppProvider extends ScanningAppProvider
// instead of setting the WebAppContext.setTempDirectory(File).
// If we used .setTempDirectory(File) all webapps will wind up in the
// same temp / work directory, overwriting each others work.
webapp.setAttribute(WebAppContext.BASETEMPDIR, _tempDirectory);
webapp.setAttribute(Server.BASE_TEMP_DIR_ATTR, _tempDirectory);
}
*/
}
@Override
@ -273,10 +302,13 @@ public class WebAppProvider extends ScanningAppProvider
{
Thread.currentThread().setContextClassLoader(environment.getClassLoader());
Resource resource = Resource.newResource(app.getOriginId());
File file = resource.getFile();
Resource resource = Resource.newResource(app.getFilename());
if (!resource.exists())
throw new IllegalStateException("App resource does not exist " + resource);
resource = unpack(resource);
File file = resource.getFile();
final String contextName = file.getName();
@ -322,12 +354,16 @@ public class WebAppProvider extends ScanningAppProvider
}
// Build the web application
ContextHandler webAppContext = null; // TODO new WebAppContext();
webAppContext.setBaseResource(Resource.newResource(file.getAbsoluteFile()));
initializeContextPath(webAppContext, contextName, !file.isDirectory());
initializeWebAppContextDefaults(webAppContext);
@SuppressWarnings("unchecked")
Class<? extends ContextHandler> contextHandlerClass = (Class<? extends ContextHandler>)environment.getAttribute("contextHandlerClass");
if (contextHandlerClass == null)
throw new IllegalStateException("Unknown ContextHandler class for " + app);
ContextHandler contextHandler = contextHandlerClass.getDeclaredConstructor().newInstance();
contextHandler.setBaseResource(Resource.newResource(file.getAbsoluteFile()));
initializeContextPath(contextHandler, contextName, !file.isDirectory());
initializeWebAppContextDefaults(contextHandler);
return webAppContext;
return contextHandler;
}
finally
{
@ -446,6 +482,10 @@ public class WebAppProvider extends ScanningAppProvider
if (exists(file.getName() + ".war") || exists(file.getName() + ".WAR"))
return; //assume we will get added events for the war file
//is there .jar file of the same name?
if (exists(file.getName() + ".jar") || exists(file.getName() + ".JAR"))
return; //assume we will get added events for the jar file
super.fileAdded(filename);
return;
}
@ -505,4 +545,90 @@ public class WebAppProvider extends ScanningAppProvider
super.fileRemoved(filename);
}
public Resource unpack(Resource resourceBase) throws IOException
{
// Accept aliases for WAR files
if (resourceBase.isAlias())
{
if (LOG.isDebugEnabled())
LOG.debug("{} anti-aliased to {}", resourceBase, resourceBase.getAlias());
URI alias = resourceBase.getAlias();
resourceBase.close();
resourceBase = Resource.newResource(alias);
}
if (!isExtract() || resourceBase.isDirectory() || resourceBase.getFile() == null)
return resourceBase;
// is the extension a known extension
if (!resourceBase.getFile().getName().toLowerCase().endsWith(".war") &&
!resourceBase.getFile().getName().toLowerCase().endsWith(".jar"))
return resourceBase;
// Track the original web_app Resource, as this could be a PathResource.
// Later steps force the Resource to be a JarFileResource, which introduces
// URLConnection caches in such a way that it prevents Hot Redeployment
// on MS Windows.
Resource originalResource = resourceBase;
// Look for unpacked directory
Path path = resourceBase.getPath();
String name = path.getName(path.getNameCount() - 1).toString();
name = name.substring(0, name.length() - 4);
Path directory = path.getParent(); // TODO support unpacking to temp or work directory
File unpacked = directory.resolve(name).toFile();
File extractLock = directory.resolve(".extract_lock").toFile();
if (!Files.isWritable(directory))
{
LOG.warn("!Writable {} -> {}", resourceBase, directory);
return resourceBase;
}
// Does the directory already exist and is newer than the packed file?
if (unpacked.exists())
{
// If it is not a directory, then we can't unpack
if (!unpacked.isDirectory())
{
LOG.warn("Unusable {} -> {}", resourceBase, unpacked);
return resourceBase;
}
// If it is newer than the resource base and there is no partial extraction, then use it.
if (Files.getLastModifiedTime(directory).toMillis() >= resourceBase.lastModified() && !extractLock.exists())
{
if (LOG.isDebugEnabled())
LOG.debug("Reuse {} -> {}", resourceBase, unpacked);
resourceBase.close();
return Resource.newResource(unpacked);
}
extractLock.createNewFile();
IO.delete(unpacked);
}
else
{
extractLock.createNewFile();
}
if (!unpacked.mkdir())
{
LOG.warn("Cannot Create {} -> {}", resourceBase, unpacked);
extractLock.delete();
return resourceBase;
}
LOG.debug("Unpack {} -> {}", resourceBase, unpacked);
try (Resource jar = JarResource.newJarResource(resourceBase))
{
jar.copyTo(unpacked);
}
extractLock.delete();
resourceBase.close();
return Resource.newResource(unpacked);
}
}

View File

@ -58,7 +58,7 @@ public class DeploymentManagerTest
// Test app get
App actual = depman.getAppByOriginId("mock-foo-webapp-1.war");
assertNotNull(actual, "Should have gotten app (by id)");
assertEquals("mock-foo-webapp-1.war", actual.getOriginId(), "Should have gotten app (by id)");
assertEquals("mock-foo-webapp-1.war", actual.getFilename(), "Should have gotten app (by id)");
}
@Test

View File

@ -49,7 +49,7 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider
{
ContextHandler contextHandler = new ContextHandler();
File war = new File(webappsDir, app.getOriginId().substring(5));
File war = new File(webappsDir, app.getFilename().substring(5));
String path = war.getName();

View File

@ -61,6 +61,7 @@ import org.slf4j.LoggerFactory;
public class Server extends Handler.Wrapper implements Attributes
{
public static final String BASE_TEMP_DIR_ATTR = "org.eclipse.jetty.server.BaseTempDir";
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
private static final String __serverInfo = "jetty/" + Server.getVersion();

View File

@ -47,13 +47,11 @@ import org.eclipse.jetty.ee10.servlet.security.ConstraintAware;
import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping;
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
import org.eclipse.jetty.ee10.servlet.security.SecurityHandler;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.ClassLoaderDump;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.TopologicalSort;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -74,73 +72,6 @@ import org.slf4j.LoggerFactory;
* the default being {@link WebXmlConfiguration} and
* {@link JettyWebXmlConfiguration}.
*
*
* <p>
* The Start/Configuration of a WebAppContext is rather complex so as to allow
* pluggable behaviour to be added in almost arbitrary ordering. The
* sequence of a WebappContext start is as follows:
* <blockquote>
* {@link #doStart()}:
* <ul>
* <li>{@link #preConfigure()}
* <ul>
* <li>Add all Server class inclusions from all known configurations {@link Configurations#getKnown()}</li>
* <li>{@link #loadConfigurations()}, which uses either explicitly set Configurations or takes the server
* default (which is all known {@link Configuration#isEnabledByDefault()} Configurations.</li>
* <li>Sort the configurations using {@link TopologicalSort} in {@link Configurations#sort()}.</li>
* <li>Add all Server class exclusions from this webapps {@link Configurations}</li>
* <li>Add all System classes inclusions and exclusions for this webapps {@link Configurations}</li>
* <li>Instantiate the WebAppClassLoader (if one not already explicitly set)</li>
* <li>{@link Configuration#preConfigure(WebAppContext)} which calls
* {@link Configuration#preConfigure(WebAppContext)} for this webapps {@link Configurations}</li>
* </ul>
* </li>
* <li>{@link ServletContextHandler#doStart()}
* <ul>
* <li>{@link org.eclipse.jetty.server.handler.ContextHandler#doStart()}
* <ul>
* <li>Init {@link MimeTypes}</li>
* <li>enterScope
* <ul>
* <li>{@link #startContext()}
* <ul>
* <li>{@link #configure()}
* <ul>
* <li>Call {@link Configuration#configure(WebAppContext)} on enabled {@link Configurations}</li>
* </ul>
* </li>
* <li>{@link MetaData#resolve(WebAppContext)}</li>
* <li>{@link #startContext()}
* <li>QuickStart may generate here and/or abort start
* <ul>
* <li>{@link ServletContextHandler#startContext}
* <ul>
* <li>Decorate listeners</li>
* <li>{@link org.eclipse.jetty.server.handler.ContextHandler#startContext}
* <ul>
* <li>add {@link org.eclipse.jetty.ee10.servlet.ManagedAttributeListener}</li>
* <li>{@link AbstractHandler#doStart}</li>
* <li>{@link #callContextInitialized(jakarta.servlet.ServletContextListener, jakarta.servlet.ServletContextEvent)}</li>
* </ul>
* </li>
* <li>{@link ServletHandler#initialize()}</li>
* </ul>
* </li>
* </ul>
* </li>
* </ul>
* </li>
* </ul>
* </li>
* <li>exitScope</li>
* </ul>
* </li>
* </ul>
* </li>
* <li>{@link #postConfigure()}</li>
* </ul>
*
* </blockquote>
*/
@ManagedObject("Web Application ContextHandler")
public class WebAppContext extends ServletContextHandler implements WebAppClassLoader.Context
@ -148,7 +79,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
static final Logger LOG = LoggerFactory.getLogger(WebAppContext.class);
public static final String TEMPDIR = ServletContext.TEMPDIR;
public static final String BASETEMPDIR = "org.eclipse.jetty.webapp.basetempdir";
public static final String BASETEMPDIR = Server.BASE_TEMP_DIR_ATTR;
public static final String WEB_DEFAULTS_XML = "org/eclipse/jetty/ee10/webapp/webdefault.xml";
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
public static final String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses";

View File

@ -131,7 +131,7 @@ public class WebInfConfiguration extends AbstractConfiguration
* B. Create a directory based on global settings. The new directory
* will be called <code>"Jetty-"+host+"-"+port+"__"+context+"-"+virtualhost+"-"+randomdigits+".dir"</code>
* <p>
* If the user has specified the context attribute org.eclipse.jetty.webapp.basetempdir, the
* If the user has specified the context attribute {@link Server#BASE_TEMP_DIR_ATTR}, the
* directory specified by this attribute will be the parent of the temp dir created. Otherwise,
* the parent dir is <code>${java.io.tmpdir}</code>. Set delete on exit depends on value of persistTempDirectory.
*