//
// ========================================================================
// Copyright (c) 1995-2017 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.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.start.Props.Prop;
import org.eclipse.jetty.start.builders.StartDirBuilder;
import org.eclipse.jetty.start.builders.StartIniBuilder;
import org.eclipse.jetty.start.fileinits.BaseHomeFileInitializer;
import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer;
import org.eclipse.jetty.start.fileinits.LocalFileInitializer;
import org.eclipse.jetty.start.fileinits.TestFileInitializer;
import org.eclipse.jetty.start.fileinits.UriFileInitializer;
/**
* 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
* @param props The properties to substitute into a template
* @return The ini file if module was added, null if module was not added.
* @throws IOException if unable to add the module
*/
public String addModule(Module module, Props props) 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())
{
// Copy from basehome
fileInitializers.add(new BaseHomeFileInitializer(baseHome));
// Handle local directories
fileInitializers.add(new LocalFileInitializer(baseHome));
// No downloads performed
fileInitializers.add(new TestFileInitializer(baseHome));
}
else if (args.isCreateFiles())
{
// Handle local directories
fileInitializers.add(new LocalFileInitializer(baseHome));
// Downloads are allowed to be performed
// Setup Maven Local Repo
Path localRepoDir = args.findMavenLocalRepoDir();
if (localRepoDir != null)
{
// Use provided local repo directory
fileInitializers.add(new MavenLocalRepoFileInitializer(baseHome,localRepoDir,args.getMavenLocalRepoDir()==null));
}
else
{
// No no local repo directory (direct downloads)
fileInitializers.add(new MavenLocalRepoFileInitializer(baseHome));
}
// Copy from basehome
fileInitializers.add(new BaseHomeFileInitializer(baseHome));
// Normal URL downloads
fileInitializers.add(new UriFileInitializer(baseHome));
}
}
/**
* 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();
// Select all the added modules to determine which ones are newly enabled
Set<String> newly_added = new HashSet<>();
if (!startArgs.getStartModules().isEmpty())
{
for (String name:startArgs.getStartModules())
{
newly_added.addAll(modules.enable(name,"--add-to-start"));
if (!newly_added.contains(name))
{
Set<String> sources = modules.get(name).getEnableSources();
sources.remove("--add-to-start");
StartLog.info("%s already enabled by %s",name,sources);
}
}
}
if (StartLog.isDebugEnabled())
StartLog.debug("added=%s",newly_added);
// Check the licenses
if (startArgs.isLicenseCheckRequired())
{
Licensing licensing = new Licensing();
for (String name : newly_added)
licensing.addModule(modules.get(name));
if (licensing.hasLicenses())
{
if (startArgs.isApproveAllLicenses())
{
StartLog.info("All Licenses Approved via Command Line Option");
}
else if (!licensing.acknowledgeLicenses())
{
StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
System.exit(1);
}
}
}
// generate the files
List<FileArg> files = new ArrayList<FileArg>();
AtomicReference<BaseBuilder.Config> builder = new AtomicReference<>();
AtomicBoolean modified = new AtomicBoolean();
Path startd = getBaseHome().getBasePath("start.d");
Path startini = getBaseHome().getBasePath("start.ini");
if (startArgs.isCreateStartd() && !Files.exists(startd))
{
if(FS.ensureDirectoryExists(startd))
{
StartLog.log("MKDIR",baseHome.toShortForm(startd));
modified.set(true);
}
if (Files.exists(startini))
{
int ini=0;
Path startd_startini=startd.resolve("start.ini");
while(Files.exists(startd_startini))
{
ini++;
startd_startini=startd.resolve("start"+ini+".ini");
}
Files.move(startini,startd_startini);
modified.set(true);
}
}
if (!newly_added.isEmpty())
{
if (Files.exists(startini) && Files.exists(startd))
StartLog.warn("Use both %s and %s is deprecated",getBaseHome().toShortForm(startd),getBaseHome().toShortForm(startini));
boolean useStartD=Files.exists(startd);
builder.set(useStartD?new StartDirBuilder(this):new StartIniBuilder(this));
newly_added.stream().map(n->modules.get(n)).forEach(module ->
{
String ini=null;
try
{
if (module.isSkipFilesValidation())
{
StartLog.debug("Skipping [files] validation on %s",module.getName());
}
else
{
// if (explictly added and ini file modified)
if (startArgs.getStartModules().contains(module.getName()))
{
ini=builder.get().addModule(module, startArgs.getProperties());
if (ini!=null)
modified.set(true);
}
for (String file : module.getFiles())
files.add(new FileArg(module,startArgs.getProperties().expand(file)));
}
}
catch(Exception e)
{
throw new RuntimeException(e);
}
if (module.isDynamic())
{
for (String s:module.getEnableSources())
StartLog.info("%-15s %s",module.getName(),s);
}
else if (module.isTransitive())
{
if (module.hasIniTemplate())
StartLog.info("%-15s transitively enabled, ini template available with --add-to-start=%s",
module.getName(),
module.getName());
else
StartLog.info("%-15s transitively enabled",module.getName());
}
else
StartLog.info("%-15s initialized in %s",
module.getName(),
ini);
});
}
files.addAll(startArgs.getFiles());
if (!files.isEmpty() && processFileResources(files))
modified.set(Boolean.TRUE);
return modified.get();
}
public BaseHome getBaseHome()
{
return baseHome;
}
public StartArgs getStartArgs()
{
return startArgs;
}
/**
* Process a specific file resource
*
* @param arg the fileArg 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) throws IOException
{
URI uri = arg.uri==null?null:URI.create(arg.uri);
if (startArgs.isCreateFiles())
{
for (FileInitializer finit : fileInitializers)
{
if (finit.isApplicable(uri))
return finit.create(uri,arg.location);
}
throw new IOException(String.format("Unable to create %s",arg));
}
for (FileInitializer finit : fileInitializers)
{
if (finit.isApplicable(uri))
if (!finit.check(uri,arg.location))
startArgs.setRun(false);
}
return false;
}
/**
* 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)
{
try
{
boolean processed = processFileResource(arg);
dirty |= processed;
}
catch (Throwable t)
{
StartLog.warn(t);
failures.add(String.format("[%s] %s - %s",t.getClass().getSimpleName(),t.getMessage(),arg.location));
}
}
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;
}
}