Skip to content

Commit 05b37d0

Browse files
committed
LDEV-6105 - Add configurable Maven download policy (ignore/warn/error) with separate startup and runtime settings
1 parent 99b9e29 commit 05b37d0

File tree

5 files changed

+121
-40
lines changed

5 files changed

+121
-40
lines changed

core/src/main/java/lucee/runtime/config/maven/MavenUpdateProvider.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import lucee.runtime.converter.ConverterException;
3939
import lucee.runtime.converter.JSONConverter;
4040
import lucee.runtime.converter.JSONDateFormat;
41+
import lucee.runtime.engine.CFMLEngineImpl;
4142
import lucee.runtime.exp.PageException;
4243
import lucee.runtime.listener.SerializationSettings;
4344
import lucee.runtime.op.CastImpl;
@@ -120,6 +121,7 @@ private static Repository[] readReposFromEnvVar(String envVarName, Repository[]
120121
repos.add(new Repository(null, new URL(s).toExternalForm(), Repository.TIMEOUT_5MINUTES, Repository.TIMEOUT_NEVER));
121122
}
122123
catch (Exception e) {
124+
LogUtil.log(Log.LEVEL_WARN, "MavenUpdateProvider", "Invalid repository URL [" + s + "] in environment variable [" + envVarName + "]: " + e.getMessage());
123125
}
124126
}
125127
if (repos.size() > 0) {
@@ -212,7 +214,8 @@ public List<Version> list() throws IOException, GeneralSecurityException, SAXExc
212214
Exception e = exceptions.pop();
213215
if (e instanceof GeneralSecurityException) throw (GeneralSecurityException) e;
214216
else if (e instanceof SAXException) throw (SAXException) e;
215-
throw ExceptionUtil.toIOException(e);
217+
218+
throw ExceptionUtil.toIOException(new IOException("Failed to list available versions from Maven repositories for [" + group + ":" + artifact + "]", e));
216219
}
217220

218221
// Join all threads
@@ -229,14 +232,15 @@ public List<Version> list() throws IOException, GeneralSecurityException, SAXExc
229232
return new ArrayList<>();
230233
}
231234
catch (UnknownHostException uhe) {
232-
throw new IOException("cannot reach maven server", uhe);
235+
throw new IOException("Cannot reach Maven server [" + uhe.getMessage() + "] " + "while resolving [" + group + ":" + artifact + "]. "
236+
+ "Check your network connectivity and DNS configuration.", uhe);
233237
}
234238
}
235239

236240
public InputStream getCore(Version version) throws IOException, GeneralSecurityException, SAXException, PageException {
237-
238241
Map<String, Object> data = detail(version, "jar", true);
239242
String strURL = Caster.toString(data.get("lco"), null);
243+
assertDownloadAllowed(strURL);
240244
if (!StringUtil.isEmpty(strURL)) {
241245
// Use HTTPDownloader with DEBUG logging for Maven operations
242246
return HTTPDownloader.get(new URL(strURL), null, null, CONNECTION_TIMEOUT, READ_TIMEOUT, null, Log.LEVEL_TRACE);
@@ -247,10 +251,14 @@ public InputStream getCore(Version version) throws IOException, GeneralSecurityE
247251
public InputStream getLoader(Version version) throws IOException, GeneralSecurityException, SAXException, PageException {
248252
Map<String, Object> data = detail(version, "jar", true);
249253
String strURL = Caster.toString(data.get("jar"), null);
250-
if (StringUtil.isEmpty(strURL)) throw new IOException("no jar for [" + version + "] found.");
254+
if (StringUtil.isEmpty(strURL)) {
255+
throw new IOException("No JAR artifact found for [" + group + ":" + artifact + ":" + version + "]. " + "Verify the version exists in the configured repositories.");
256+
}
251257

252258
// Use HTTPDownloader with DEBUG logging for Maven operations
253-
return HTTPDownloader.get(new URL(strURL), null, null, CONNECTION_TIMEOUT, READ_TIMEOUT, null, Log.LEVEL_TRACE);
259+
URL url = new URL(strURL);
260+
assertDownloadAllowed(strURL);
261+
return HTTPDownloader.get(url, null, null, CONNECTION_TIMEOUT, READ_TIMEOUT, null, Log.LEVEL_TRACE);
254262
}
255263

256264
/*
@@ -302,7 +310,8 @@ public Map<String, Object> detail(Version version, String requiredArtifactExtens
302310
}
303311
// read main
304312
{
305-
URL urlMain = new URL(repo.url + g + "/" + a + "/" + v + "/" + a + "-" + v + "." + requiredArtifactExtension);
313+
String strURL = repo.url + g + "/" + a + "/" + v + "/" + a + "-" + v + "." + requiredArtifactExtension;
314+
URL urlMain = new URL(strURL);
306315
HTTPResponse rsp = HTTPDownloader.head(urlMain, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT, Log.LEVEL_TRACE);
307316
if (validSatusCode(rsp)) {
308317
Map<String, Object> result = new LinkedHashMap<>();
@@ -341,8 +350,8 @@ public Map<String, Object> detail(Version version, String requiredArtifactExtens
341350
catch (UnknownHostException uhe) {
342351
throw new IOException("cannot reach maven server", uhe);
343352
}
344-
if (throwException) throw new IOException("could not find the artifact [" + requiredArtifactExtension + "] for [" + group + ":" + artifact + ":" + version
345-
+ "] in the following repositories [" + toList(repos) + "]");
353+
if (throwException) throw new IOException("Could not find the artifact [" + group + ":" + artifact + ":" + version + "] (type: " + requiredArtifactExtension
354+
+ ") in any of the configured repositories: [" + toList(repos) + "]. " + "Verify the artifact coordinates and version are correct.");
346355
return null;
347356
}
348357

@@ -494,4 +503,17 @@ public String toString() {
494503
return "label:" + label + ";url:" + url + ";timeoutList:" + timeoutList + ";timeoutDetail:" + timeoutDetail;
495504
}
496505
}
506+
507+
private static void assertDownloadAllowed(String url) throws IOException {
508+
int policy = CFMLEngineImpl.getActiveDownloadPolicy();
509+
if (policy == CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_ERROR) {
510+
throw new IOException("Maven download is blocked by policy. Attempted to download [" + url + "]. "
511+
+ "To allow downloads, set the system property or environment variable " + "'lucee.maven.download.policy' to 'warn' or 'ignore'. "
512+
+ "Alternatively, place the artifact manually in your local Maven repository (~/.m2/repository).");
513+
}
514+
else if (policy == CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_WARN) {
515+
LogUtil.log(CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_LOG_LEVEL, "maven", "Downloading Maven artifact from [" + url + "]. Maven download policy is set to 'warn'. "
516+
+ "Set 'lucee.maven.download.policy' to 'error' to block downloads " + "or 'ignore' to suppress this warning.");
517+
}
518+
}
497519
}

core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,14 @@ public final class CFMLEngineImpl implements CFMLEngine {
189189

190190
private static final boolean IS_WINDOWS = SystemUtil.isWindows();
191191

192+
public static final int MAVEN_DOWNLOAD_POLICY_STARTUP;
193+
public static final int MAVEN_DOWNLOAD_POLICY_RUNTIME;
194+
public static final int MAVEN_DOWNLOAD_POLICY_LOG_LEVEL;
195+
196+
public static final int MAVEN_DOWNLOAD_POLICY_ERROR = 2;
197+
public static final int MAVEN_DOWNLOAD_POLICY_WARN = 1;
198+
public static final int MAVEN_DOWNLOAD_POLICY_IGNORE = 0;
199+
192200
static {
193201
System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
194202
System.setProperty("javax.xml.bind.context.factory", "com.sun.xml.bind.v2.ContextFactory");
@@ -222,6 +230,30 @@ public final class CFMLEngineImpl implements CFMLEngine {
222230

223231
// Disable reverse DNS lookups
224232
System.setProperty("sun.net.useExclusiveBind", "false");
233+
234+
String tmp = Caster.toString(SystemUtil.getSystemPropOrEnvVar("lucee.maven.download.policy.startup", null), null);
235+
if ("error".equals(tmp)) {
236+
MAVEN_DOWNLOAD_POLICY_STARTUP = MAVEN_DOWNLOAD_POLICY_ERROR;
237+
}
238+
else if ("warn".equals(tmp) || "warning".equals(tmp)) {
239+
MAVEN_DOWNLOAD_POLICY_STARTUP = MAVEN_DOWNLOAD_POLICY_WARN;
240+
}
241+
else {
242+
MAVEN_DOWNLOAD_POLICY_STARTUP = MAVEN_DOWNLOAD_POLICY_IGNORE;
243+
}
244+
245+
tmp = Caster.toString(SystemUtil.getSystemPropOrEnvVar("lucee.maven.download.policy.runtime", null), null);
246+
if ("error".equals(tmp)) {
247+
MAVEN_DOWNLOAD_POLICY_RUNTIME = MAVEN_DOWNLOAD_POLICY_ERROR;
248+
}
249+
else if ("warn".equals(tmp) || "warning".equals(tmp)) {
250+
MAVEN_DOWNLOAD_POLICY_RUNTIME = MAVEN_DOWNLOAD_POLICY_WARN;
251+
}
252+
else {
253+
MAVEN_DOWNLOAD_POLICY_RUNTIME = MAVEN_DOWNLOAD_POLICY_IGNORE;
254+
}
255+
256+
MAVEN_DOWNLOAD_POLICY_LOG_LEVEL = LogUtil.toLevel(Caster.toString(SystemUtil.getSystemPropOrEnvVar("lucee.maven.download.policy.log.level", null), null), Log.LEVEL_ERROR);
225257
}
226258

227259
public static final PrintStream CONSOLE_ERR = System.err;
@@ -274,25 +306,22 @@ private CFMLEngineImpl(CFMLEngineFactory factory, BundleCollection bc) {
274306
try {
275307
FunctionLibFactory.loadFromSystem(null);
276308
}
277-
catch (FunctionLibException e) {
278-
}
309+
catch (FunctionLibException e) {}
279310
}, true).start();
280311

281312
ThreadUtil.getThread(() -> {
282313
try {
283314
TagLibFactory.loadFromSystem(null);
284315
}
285-
catch (TagLibException e) {
286-
}
316+
catch (TagLibException e) {}
287317
}, true).start();
288318

289319
// Force localhost resolution early
290320
ThreadUtil.getThread(() -> {
291321
try {
292322
Log4j2Engine.prepare();
293323
}
294-
catch (Exception e) {
295-
}
324+
catch (Exception e) {}
296325
}, true).start();
297326

298327
/*
@@ -528,8 +557,7 @@ else if (installExtensions && (updateInfo.updateType == ConfigFactory.NEW_MINOR
528557
try {
529558
log = cs.getLog("deploy", true);
530559
}
531-
catch (PageException e) {
532-
}
560+
catch (PageException e) {}
533561
}
534562

535563
touchMonitor(cs);
@@ -555,6 +583,18 @@ else if (installExtensions && (updateInfo.updateType == ConfigFactory.NEW_MINOR
555583
LogUtil.log(cs, Log.LEVEL_INFO, "startup", "Start CFML Controller");
556584
controler.start();
557585
}
586+
587+
}
588+
589+
public static int getActiveDownloadPolicy() {
590+
try {
591+
CFMLEngine eng = CFMLEngineFactory.getInstance();
592+
if (eng != null && eng.uptime() > 0) return MAVEN_DOWNLOAD_POLICY_RUNTIME;
593+
}
594+
catch (Exception e) {
595+
// engine not registered yet = still starting up
596+
}
597+
return MAVEN_DOWNLOAD_POLICY_STARTUP;
558598
}
559599

560600
private static void checkInvalidExtensions(CFMLEngineImpl eng, ConfigPro config, Set<ExtensionDefintion> extensionsToInstall, Set<String> extensionsToRemove) {
@@ -668,8 +708,7 @@ private int deployBundledExtension(ConfigServerImpl cs, boolean validate) {
668708
try {
669709
existingMap.put(ed.getSource().getName(), ed);
670710
}
671-
catch (ApplicationException e) {
672-
}
711+
catch (ApplicationException e) {}
673712
}
674713
}
675714

@@ -1776,8 +1815,7 @@ private void onStartCall(ConfigPro config, boolean reload, boolean warmup) {
17761815
Resource rootdir = config.getRootDirectory();
17771816
listenerTemplateCFMLWebRoot = rootdir.getRealResource(context + "." + lucee.runtime.config.Constants.getCFMLComponentExtension());
17781817
}
1779-
catch (Exception e) {
1780-
}
1818+
catch (Exception e) {}
17811819
}
17821820

17831821
// dialect

core/src/main/java/lucee/runtime/mvn/MavenUtil.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import lucee.commons.net.HTTPUtil;
4545
import lucee.runtime.config.Config;
4646
import lucee.runtime.config.ConfigPro;
47+
import lucee.runtime.engine.CFMLEngineImpl;
4748
import lucee.runtime.exp.ApplicationException;
4849
import lucee.runtime.mvn.POMReader.Dependency;
4950
import lucee.runtime.op.Caster;
@@ -220,7 +221,8 @@ public static POM getDependency(Resource localDirectory, POMReader.Dependency rd
220221
// v = resolvePlaceholders(pdm, v, pdm.getProperties());
221222
}
222223
if (v == null) {
223-
throw new IOException("could not find version for dependency [" + g + ":" + a + "] in [" + current + "]");
224+
throw new IOException("No version defined for dependency [" + g + ":" + a + "] in POM [" + current + "]. "
225+
+ "Specify a version directly or ensure it is declared in <dependencyManagement>.");
224226
}
225227
}
226228

@@ -503,7 +505,8 @@ else if ("optional".equals(placeholder)) {
503505
if (!modifed) break;
504506
}
505507
if (value != null && value.indexOf("${") != -1) {
506-
throw new IOException("Cannot resolve [" + value + "] for [" + pom + "], available properties are [" + ListUtil.toList(properties.keySet(), ", ") + "]");
508+
throw new IOException("Cannot resolve placeholder in [" + value + "] for POM [" + pom + "]. " + "Available properties: [" + ListUtil.toList(properties.keySet(), ", ")
509+
+ "]. " + "Ensure the property is defined in the POM, parent POM, or system properties.");
507510
}
508511
return value;
509512
}
@@ -517,8 +520,7 @@ public static void downloadAsync(POM pom, Collection<Repository> repositories, S
517520
try {
518521
download(pom, repositories, type, log);
519522
}
520-
catch (IOException e) {
521-
}
523+
catch (IOException e) {}
522524
}, true).start();
523525
}
524526

@@ -527,7 +529,7 @@ public static Resource download(POM pom, Collection<Repository> repositories, St
527529

528530
// file is empty or does not exist
529531
if (!res.isFile()) {
530-
// print.ds("--->" + pom.toString());
532+
531533
synchronized (SystemUtil.createToken("mvn", res.getAbsolutePath())) {
532534
// file is empty or does not exist
533535
if (!res.isFile()) {
@@ -553,7 +555,10 @@ public static Resource download(POM pom, Collection<Repository> repositories, St
553555
download(pom, repositories, type, log);
554556
return res;
555557
}
556-
throw new IOException("Failed to download [" + pom + "] ");
558+
559+
throw new IOException("Maven artifact [" + pom.getGroupId() + ":" + pom.getArtifactId() + ":" + pom.getVersion() + "] "
560+
+ "is not available. A previous download attempt failed and is cached for " + (ARTIFACT_UNAVAILABLE_CACHE_DURATION / 60000) + " minutes. "
561+
+ "Delete the '.lastUpdated' file in the local cache to retry immediately.");
557562
}
558563

559564
String scriptName = pom.getGroupId().replace('.', '/') + "/" + pom.getArtifactId() + "/" + pom.getVersion() + "/" + pom.getArtifactId() + "-" + pom.getVersion()
@@ -563,19 +568,30 @@ public static Resource download(POM pom, Collection<Repository> repositories, St
563568
if (repositories == null || repositories.isEmpty()) repositories = pom.getRepositories();
564569

565570
if (repositories == null || repositories.size() == 0) {
566-
IOException ioe = new IOException("Failed to download java artifact [" + pom.toString() + "] for type [" + type + "]");
567-
// "Failed to download java artifact [" + pom.toString() + "] for type [" + type + "], attempted
568-
// endpoint(s): [" + sb + "]");
569-
// if (cause != null) ExceptionUtil.initCauseEL(ioe, cause);
570-
throw ioe;
571+
throw new IOException("Failed to download Maven artifact [" + pom.getGroupId() + ":" + pom.getArtifactId() + ":" + pom.getVersion() + "] " + "(type: "
572+
+ type + "). No repositories are configured. " + "Ensure at least one repository is defined in the POM or Lucee configuration.");
571573
}
572574
// url = pom.getArtifact(type, repositories);
573575
//////// if (log != null) log.info("maven", "download [" + url + "]");
574576
URL url;
575577
CloseableHttpClient httpClient;
578+
int policy = CFMLEngineImpl.getActiveDownloadPolicy();
576579
for (Repository r: sort(repositories)) {
577580
url = null;
578581
httpClient = null;
582+
if (policy == CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_ERROR) {
583+
throw new IOException("Lucee is unable to resolve the Maven artifact [" + pom.getGroupId() + ":" + pom.getArtifactId() + ":" + pom.getVersion()
584+
+ "] " + "(type: " + type + ") because Maven downloads are blocked by policy. "
585+
+ "To allow downloads, set the system property or environment variable " + "'lucee.maven.download.policy' to 'warn' or 'ignore'. ");
586+
}
587+
else if (policy == CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_WARN) {
588+
LogUtil.log(CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_LOG_LEVEL, "maven",
589+
"Downloading Maven artifact [" + pom.getGroupId() + ":" + pom.getArtifactId() + ":" + pom.getVersion() + "] " + "(type: " + type
590+
+ "). Maven download policy is set to 'warn'. "
591+
+ "Set the system property or environment variable 'lucee.maven.download.policy' to "
592+
+ "'error' to block downloads or 'ignore' to suppress this warning.");
593+
}
594+
579595
try {
580596
url = new URL(r.getUrl() + scriptName);
581597
httpClient = HttpClients.createDefault();
@@ -634,13 +650,16 @@ public static Resource download(POM pom, Collection<Repository> repositories, St
634650
}
635651
catch (IOException ioe) {
636652
createLastUpdated(res, info);
637-
IOException ex = new IOException("Failed to download [ " + pom + ":" + type + "]");
653+
IOException ex = new IOException("Failed to download Maven artifact [" + pom.getGroupId() + ":" + pom.getArtifactId() + ":" + pom.getVersion() + "] "
654+
+ "(type: " + type + "). Check network connectivity and repository availability.");
638655
ExceptionUtil.initCauseEL(ex, ioe);
639656
// MUST add again ResourceUtil.deleteEmptyFoldersInside(pom.getLocalDirectory());
640657
throw ex;
641658
}
642659
createLastUpdated(res, info);
643-
throw new IOException("Failed to download [" + pom + ":" + type + "] from " + repositories.size() + " repositories");
660+
throw new IOException("Failed to download Maven artifact [" + pom.getGroupId() + ":" + pom.getArtifactId() + ":" + pom.getVersion() + "] " + "(type: " + type
661+
+ ") after trying all " + repositories.size() + " configured repositories. "
662+
+ "Verify the artifact coordinates are correct and the repositories are accessible.");
644663
}
645664
}
646665
}
@@ -703,17 +722,20 @@ public static POM toPOM(Resource localDirectory, Collection<Repository> reposito
703722
}
704723

705724
public static int toScopes(String scopes) throws IOException {
706-
if (StringUtil.isEmpty(scopes, true)) throw new IOException("there is no scope defined");
725+
if (StringUtil.isEmpty(scopes, true))
726+
throw new IOException("No Maven dependency scope defined. " + "Specify at least one scope from: compile, test, provided, runtime, system, import.");
707727
return toScopes(ListUtil.listToStringArray(scopes, ','));
708728
}
709729

710730
public static int toScopes(String[] scopes) throws IOException {
711-
if (scopes.length == 0) throw new IOException("there is no scope defined");
731+
if (scopes.length == 0) throw new IOException("No Maven dependency scope defined. " + "Specify at least one scope from: compile, test, provided, runtime, system, import.");
712732

713733
int rtn = 0, tmp;
714734
for (String scope: scopes) {
715735
tmp = toScope(scope, 0);
716-
if (tmp == 0) throw new IOException("scope [" + scope + "] is not a supported scope, valid scope names are [compile,test,provided,runtime,system,import]");
736+
if (tmp == 0) {
737+
throw new IOException("Invalid Maven scope [" + scope.trim() + "]. " + "Supported values are: compile, test, provided, runtime, system, import.");
738+
}
717739
rtn += tmp;
718740
}
719741
return rtn;
@@ -1018,8 +1040,7 @@ public static Struct getMetaData(Config config, Class clazz, Struct defaultValue
10181040
data.setEL(KeyConstants._location, res.getAbsolutePath());
10191041
return data;
10201042
}
1021-
catch (Exception e) {
1022-
}
1043+
catch (Exception e) {}
10231044
}
10241045
}
10251046
}

loader/build.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<project default="core" basedir="." name="Lucee"
33
xmlns:resolver="antlib:org.apache.maven.resolver.ant">
44

5-
<property name="version" value="7.0.2.81-SNAPSHOT"/>
5+
<property name="version" value="7.0.2.82-SNAPSHOT"/>
66

77
<taskdef uri="antlib:org.apache.maven.resolver.ant" resource="org/apache/maven/resolver/ant/antlib.xml">
88
<classpath>

loader/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>org.lucee</groupId>
55
<artifactId>lucee</artifactId>
6-
<version>7.0.2.81-SNAPSHOT</version>
6+
<version>7.0.2.82-SNAPSHOT</version>
77
<packaging>jar</packaging>
88

99
<name>Lucee Loader Build</name>

0 commit comments

Comments
 (0)