blob: 843d6f18033829cf8ab7d728c00b2d3b562908d6 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.start;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.start.builders.StartDirBuilder;
import org.eclipse.jetty.start.builders.StartIniBuilder;
import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer;
import org.eclipse.jetty.start.fileinits.TestFileInitializer;
import org.eclipse.jetty.start.fileinits.UriFileInitializer;
import org.eclipse.jetty.start.graph.CriteriaSetPredicate;
import org.eclipse.jetty.start.graph.UniqueCriteriaPredicate;
import org.eclipse.jetty.start.graph.Predicate;
import org.eclipse.jetty.start.graph.Selection;
/**
* Build a start configuration in <code>${jetty.base}</code>, including
* ini files, directories, and libs. Also handles License management.
*/
public class BaseBuilder
{
public static interface Config
{
/**
* Add a module to the start environment in <code>${jetty.base}</code>
*
* @param module
* the module to add
* @return true if module was added, false if module was not added
* (because that module already exists)
* @throws IOException if unable to add the module
*/
public boolean addModule(Module module) throws IOException;
}
private static final String EXITING_LICENSE_NOT_ACKNOWLEDGED = "Exiting: license not acknowledged!";
private final BaseHome baseHome;
private final List<FileInitializer> fileInitializers;
private final StartArgs startArgs;
public BaseBuilder(BaseHome baseHome, StartArgs args)
{
this.baseHome = baseHome;
this.startArgs = args;
this.fileInitializers = new ArrayList<>();
// Establish FileInitializers
if (args.isTestingModeEnabled())
{
// No downloads performed
fileInitializers.add(new TestFileInitializer());
}
else if (args.isDownload())
{
// Downloads are allowed to be performed
// Setup Maven Local Repo
Path localRepoDir = args.getMavenLocalRepoDir();
if (localRepoDir != null)
{
// Use provided local repo directory
fileInitializers.add(new MavenLocalRepoFileInitializer(baseHome,localRepoDir));
}
else
{
// No no local repo directory (direct downloads)
fileInitializers.add(new MavenLocalRepoFileInitializer(baseHome));
}
// Normal URL downloads
fileInitializers.add(new UriFileInitializer(baseHome));
}
}
private void ackLicenses() throws IOException
{
if (startArgs.isLicenseCheckRequired())
{
if (startArgs.isApproveAllLicenses())
{
StartLog.info("All Licenses Approved via Command Line Option");
}
else
{
Licensing licensing = new Licensing();
for (Module module : startArgs.getAllModules().getSelected())
{
if (!module.hasFiles(baseHome,startArgs.getProperties()))
{
licensing.addModule(module);
}
}
if (licensing.hasLicenses())
{
StartLog.debug("Requesting License Acknowledgement");
if (!licensing.acknowledgeLicenses())
{
StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
System.exit(1);
}
}
}
}
}
/**
* Build out the Base directory (if needed)
*
* @return true if base directory was changed, false if left unchanged.
* @throws IOException if unable to build
*/
public boolean build() throws IOException
{
Modules modules = startArgs.getAllModules();
boolean dirty = false;
String dirCriteria = "<add-to-startd>";
String iniCriteria = "<add-to-start-ini>";
Selection startDirSelection = new Selection(dirCriteria);
Selection startIniSelection = new Selection(iniCriteria);
List<String> startDNames = new ArrayList<>();
startDNames.addAll(startArgs.getAddToStartdIni());
List<String> startIniNames = new ArrayList<>();
startIniNames.addAll(startArgs.getAddToStartIni());
int count = 0;
count += modules.selectNodes(startDNames,startDirSelection);
count += modules.selectNodes(startIniNames,startIniSelection);
// look for ambiguous declaration found in both places
Predicate ambiguousPredicate = new CriteriaSetPredicate(dirCriteria,iniCriteria);
List<Module> ambiguous = modules.getMatching(ambiguousPredicate);
if (ambiguous.size() > 0)
{
StringBuilder warn = new StringBuilder();
warn.append("Ambiguous module locations detected, defaulting to --add-to-start for the following module selections:");
warn.append(" [");
for (int i = 0; i < ambiguous.size(); i++)
{
if (i > 0)
{
warn.append(", ");
}
warn.append(ambiguous.get(i).getName());
}
warn.append(']');
StartLog.warn(warn.toString());
}
StartLog.debug("Adding %s new module(s)",count);
// Acknowledge Licenses
ackLicenses();
// Collect specific modules to enable
// Should match 'criteria', with no other selections.explicit
Predicate startDMatcher = new UniqueCriteriaPredicate(dirCriteria);
Predicate startIniMatcher = new UniqueCriteriaPredicate(iniCriteria);
List<Module> startDModules = modules.getMatching(startDMatcher);
List<Module> startIniModules = modules.getMatching(startIniMatcher);
List<FileArg> files = new ArrayList<FileArg>();
if (!startDModules.isEmpty())
{
StartDirBuilder builder = new StartDirBuilder(this);
for (Module mod : startDModules)
{
if (ambiguous.contains(mod))
{
// skip ambiguous module
continue;
}
if (mod.isSkipFilesValidation())
{
StartLog.debug("Skipping [files] validation on %s",mod.getName());
}
else
{
dirty |= builder.addModule(mod);
for (String file : mod.getFiles())
{
files.add(new FileArg(mod,startArgs.getProperties().expand(file)));
}
}
}
}
if (!startIniModules.isEmpty())
{
StartIniBuilder builder = new StartIniBuilder(this);
for (Module mod : startIniModules)
{
if (mod.isSkipFilesValidation())
{
StartLog.debug("Skipping [files] validation on %s",mod.getName());
}
else
{
dirty |= builder.addModule(mod);
for (String file : mod.getFiles())
{
files.add(new FileArg(mod,startArgs.getProperties().expand(file)));
}
}
}
}
// Process files
files.addAll(startArgs.getFiles());
dirty |= processFileResources(files);
return dirty;
}
public BaseHome getBaseHome()
{
return baseHome;
}
public StartArgs getStartArgs()
{
return startArgs;
}
/**
* Process a specific file resource
*
* @param arg
* the fileArg to work with
* @param file
* the resolved file reference to work with
* @return true if change was made as a result of the file, false if no change made.
* @throws IOException
* if there was an issue in processing this file
*/
private boolean processFileResource(FileArg arg, Path file) throws IOException
{
if (startArgs.isDownload() && (arg.uri != null))
{
// now on copy/download paths (be safe above all else)
if (!file.startsWith(baseHome.getBasePath()))
{
throw new IOException("For security reasons, Jetty start is unable to process maven file resource not in ${jetty.base} - " + file);
}
// make the directories in ${jetty.base} that we need
FS.ensureDirectoryExists(file.getParent());
URI uri = URI.create(arg.uri);
// Process via initializers
for (FileInitializer finit : fileInitializers)
{
if (finit.init(uri,file,arg.location))
{
// Completed successfully
return true;
}
}
return false;
}
else
{
// Process directly
boolean isDir = arg.location.endsWith("/");
if (FS.exists(file))
{
// Validate existence
if (isDir)
{
if (!Files.isDirectory(file))
{
throw new IOException("Invalid: path should be a directory (but isn't): " + file);
}
if (!FS.canReadDirectory(file))
{
throw new IOException("Unable to read directory: " + file);
}
}
else
{
if (!FS.canReadFile(file))
{
throw new IOException("Unable to read file: " + file);
}
}
return false;
}
if (isDir)
{
// Create directory
StartLog.log("MKDIR",baseHome.toShortForm(file));
return FS.ensureDirectoryExists(file);
}
else
{
// Warn on missing file (this has to be resolved manually by user)
String shortRef = baseHome.toShortForm(file);
if (startArgs.isTestingModeEnabled())
{
StartLog.log("TESTING MODE","Skipping required file check on: %s",shortRef);
return true;
}
StartLog.warn("Missing Required File: %s",baseHome.toShortForm(file));
startArgs.setRun(false);
if (arg.uri != null)
{
StartLog.warn(" Can be downloaded From: %s",arg.uri);
StartLog.warn(" Run start.jar --create-files to download");
}
return true;
}
}
}
/**
* Process the {@link FileArg} for startup, assume that all licenses have
* been acknowledged at this stage.
*
* @param files
* the list of {@link FileArg}s to process
* @return true if base directory modified, false if left untouched
*/
private boolean processFileResources(List<FileArg> files) throws IOException
{
if ((files == null) || (files.isEmpty()))
{
return false;
}
boolean dirty = false;
List<String> failures = new ArrayList<String>();
for (FileArg arg : files)
{
Path file = baseHome.getBasePath(arg.location);
try
{
dirty |= processFileResource(arg,file);
}
catch (Throwable t)
{
StartLog.warn(t);
failures.add(String.format("[%s] %s - %s",t.getClass().getSimpleName(),t.getMessage(),file.toAbsolutePath().toString()));
}
}
if (!failures.isEmpty())
{
StringBuilder err = new StringBuilder();
err.append("Failed to process all file resources.");
for (String failure : failures)
{
err.append(System.lineSeparator()).append(" - ").append(failure);
}
StartLog.warn(err.toString());
throw new RuntimeException(err.toString());
}
return dirty;
}
}