Skip to content

Commit 688c839

Browse files
committed
spline #1419 Upgrade to Apache Configuration 2 due to CVE-2025-46392
1 parent 105424c commit 688c839

12 files changed

Lines changed: 678 additions & 15 deletions

File tree

.sdkmanrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Enable auto-env through the sdkman_auto_env config
22
# Add key=value pairs of SDKs to use below
3-
java=11.0.26-amzn
3+
java=11.0.27-amzn

admin/src/main/scala/za/co/absa/spline/admin/AppConfig.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@
1616

1717
package za.co.absa.spline.admin
1818

19-
import org.apache.commons.configuration.{CompositeConfiguration, PropertiesConfiguration, SystemConfiguration}
19+
import org.apache.commons.configuration2.ConfigurationImplicits._
20+
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder
21+
import org.apache.commons.configuration2.builder.fluent.Parameters
22+
import org.apache.commons.configuration2.{CompositeConfiguration, PropertiesConfiguration, SystemConfiguration}
2023
import za.co.absa.commons.config.ConfTyped
21-
import za.co.absa.commons.config.ConfigurationImplicits._
2224

2325
import java.util
2426

2527
object AppConfig
2628
extends CompositeConfiguration(util.Arrays.asList(
2729
new SystemConfiguration,
28-
new PropertiesConfiguration(ClassLoader.getSystemResource("spline-cli.properties"))
30+
new FileBasedConfigurationBuilder(classOf[PropertiesConfiguration])
31+
.configure(new Parameters().fileBased().setURL(ClassLoader.getSystemResource("spline-cli.properties")))
32+
.getConfiguration,
2933
)) with ConfTyped {
3034

3135
private val conf = this

build/parent-pom/pom.xml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,14 @@
404404
<version>3.14.0</version>
405405
</dependency>
406406
<dependency>
407-
<groupId>commons-configuration</groupId>
408-
<artifactId>commons-configuration</artifactId>
409-
<version>1.10</version>
407+
<groupId>org.apache.commons</groupId>
408+
<artifactId>commons-configuration2</artifactId>
409+
<version>2.12.0</version>
410+
</dependency>
411+
<dependency>
412+
<groupId>commons-beanutils</groupId>
413+
<artifactId>commons-beanutils</artifactId>
414+
<version>1.9.4</version>
410415
</dependency>
411416
<dependency>
412417
<groupId>commons-io</groupId>

commons/pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@
3939
<artifactId>commons_${scala.compat.version}</artifactId>
4040
</dependency>
4141
<dependency>
42-
<groupId>commons-configuration</groupId>
43-
<artifactId>commons-configuration</artifactId>
42+
<groupId>org.apache.commons</groupId>
43+
<artifactId>commons-configuration2</artifactId>
44+
</dependency>
45+
<dependency>
46+
<groupId>commons-beanutils</groupId>
47+
<artifactId>commons-beanutils</artifactId>
4448
</dependency>
4549
<dependency>
4650
<groupId>commons-io</groupId>
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/*
2+
* Copyright 2025 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.apache.commons.configuration2
18+
19+
import org.apache.commons.lang3.StringUtils.{isBlank, isNotBlank}
20+
21+
import scala.jdk.CollectionConverters._
22+
import scala.reflect.runtime.universe.{TypeTag, typeOf}
23+
import scala.util.Try
24+
25+
26+
/**
27+
* The object contains extension methods for the [[org.apache.commons.configuration2.Configuration Configuration]] interface.
28+
*/
29+
//noinspection ScalaUnusedSymbol
30+
object ConfigurationImplicits {
31+
32+
/**
33+
* The class wraps the [[org.apache.commons.configuration2.Configuration Configuration]] interface in order to provide extension methods.
34+
*
35+
* @param conf A configuration instance
36+
* @tparam T A specific type implementing the [[org.apache.commons.configuration2.Configuration Configuration]] interface
37+
*/
38+
implicit class ConfigurationRequiredWrapper[T <: Configuration](val conf: T) extends AnyVal {
39+
40+
/**
41+
* Gets a value of an object configuration property and checks whether property exists.
42+
*
43+
* @return A value of an object configuration property if exists, otherwise throws an exception.
44+
*/
45+
def getRequiredObject[A <: AnyRef]: String => A = getRequired[AnyRef](conf.getProperty, null.!=)(_).asInstanceOf[A]
46+
47+
/**
48+
* Gets a value of string configuration property and checks whether property exists.
49+
*
50+
* @return A value of string configuration property if exists, otherwise throws an exception.
51+
*/
52+
def getRequiredString: String => String = getRequired(conf.getString, isNotBlank)
53+
54+
/**
55+
* Gets a value of string array configuration property and checks whether the array is not empty.
56+
*
57+
* @return A value of string array configuration property if not empty, otherwise throws an exception.
58+
*/
59+
def getRequiredStringArray: String => Array[String] = getRequired(conf.getStringArray, (arr: Array[String]) => !arr.forall(isBlank))
60+
61+
/**
62+
* Gets a value of boolean configuration property and checks whether property exists.
63+
*
64+
* @return A value of boolean configuration property if exists, otherwise throws an exception.
65+
*/
66+
def getRequiredBoolean: String => Boolean = getRequired(conf.getBoolean(_, null), null.!=) //NOSONAR
67+
68+
/**
69+
* Gets a value of big decimal configuration property and checks whether property exists.
70+
*
71+
* @return A value of big decimal configuration property if exists, otherwise throws an exception.
72+
*/
73+
def getRequiredBigDecimal: String => BigDecimal = getRequired(conf.getBigDecimal(_, null), null.!=) //NOSONAR
74+
75+
/**
76+
* Gets a value of byte configuration property and checks whether property exists.
77+
*
78+
* @return A value of byte configuration property if exists, otherwise throws an exception.
79+
*/
80+
def getRequiredByte: String => Byte = getRequired(conf.getByte(_, null), null.!=) //NOSONAR
81+
82+
/**
83+
* Gets a value of short configuration property and checks whether property exists.
84+
*
85+
* @return A value of short configuration property if exists, otherwise throws an exception.
86+
*/
87+
def getRequiredShort: String => Short = getRequired(conf.getShort(_, null), null.!=) //NOSONAR
88+
89+
/**
90+
* Gets a value of int configuration property and checks whether property exists.
91+
*
92+
* @return A value of int configuration property if exists, otherwise throws an exception.
93+
*/
94+
def getRequiredInt: String => Int = getRequired(conf.getInteger(_, null), null.!=) //NOSONAR
95+
96+
/**
97+
* Gets a value of long configuration property and checks whether property exists.
98+
*
99+
* @return A value of long configuration property if exists, otherwise throws an exception.
100+
*/
101+
def getRequiredLong: String => Long = getRequired(conf.getLong(_, null), null.!=) //NOSONAR
102+
103+
/**
104+
* Gets a value of float configuration property and checks whether property exists.
105+
*
106+
* @return A value of float configuration property if exists, otherwise throws an exception.
107+
*/
108+
def getRequiredFloat: String => Float = getRequired(conf.getFloat(_, null), null.!=) //NOSONAR
109+
110+
/**
111+
* Gets a value of double configuration property and checks whether property exists.
112+
*
113+
* @return A value of double configuration property if exists, otherwise throws an exception.
114+
*/
115+
def getRequiredDouble: String => Double = getRequired(conf.getDouble(_, null), null.!=) //NOSONAR
116+
117+
private def getRequired[V](get: String => V, check: V => Boolean)(key: String): V = {
118+
Try(get(key))
119+
.filter(check)
120+
.recover {
121+
case _: NoSuchElementException =>
122+
// rewrite exception message for clarity
123+
throw new NoSuchElementException(s"Missing configuration property ${getFullPropName(key)}")
124+
case e: Exception =>
125+
throw new RuntimeException(s"Error in retrieving configuration property ${getFullPropName(key)}", e)
126+
}
127+
.get
128+
}
129+
130+
private def getFullPropName(key: String) = conf match {
131+
case sc: SubsetConfiguration => sc.getParentKey(key)
132+
case _ => key
133+
}
134+
}
135+
136+
/**
137+
* The class wraps the [[org.apache.commons.configuration2.Configuration Configuration]] interface in order to provide extension methods.
138+
*
139+
* @param conf A configuration instance
140+
* @tparam T A specific type implementing the [[org.apache.commons.configuration2.Configuration Configuration]] interface
141+
*/
142+
implicit class ConfigurationOptionalWrapper[T <: Configuration](val conf: T) extends AnyVal {
143+
144+
/**
145+
* Gets a value of an object configuration property.
146+
*
147+
* @return A Some wrapped value of object configuration property if exists, otherwise None.
148+
*/
149+
def getOptionalObject[A <: AnyRef]: String => Option[A] = getOptional[AnyRef](conf.getProperty)(_).map(_.asInstanceOf[A])
150+
151+
/**
152+
* Gets a value of string configuration property.
153+
*
154+
* @return A Some wrapped value of string configuration property if exists, otherwise None.
155+
*/
156+
def getOptionalString: String => Option[String] = getOptional(conf.getString)(_).filter(isNotBlank)
157+
158+
159+
/**
160+
* Gets a value of string array configuration property and checks whether the array is not empty.
161+
*
162+
* @return A Some wrapped value of string array configuration property if not empty, otherwise None.
163+
*/
164+
def getOptionalStringArray: String => Option[Array[String]] = getOptional(conf.getStringArray)(_).filter(_.nonEmpty)
165+
166+
/**
167+
* Gets a value of boolean configuration property.
168+
*
169+
* @return A Some wrapped value of boolean configuration property if exists, otherwise None.
170+
*/
171+
def getOptionalBoolean: String => Option[Boolean] = getOptional(conf.getBoolean)
172+
173+
/**
174+
* Gets a value of big decimal configuration property.
175+
*
176+
* @return A Some wrapped value of big decimal configuration property if exists, otherwise None.
177+
*/
178+
//noinspection ConvertibleToMethodValue
179+
def getOptionalBigDecimal: String => Option[BigDecimal] = getOptional(conf.getBigDecimal(_))
180+
181+
/**
182+
* Gets a value of byte configuration property.
183+
*
184+
* @return A Some wrapped value of byte configuration property if exists, otherwise None.
185+
*/
186+
def getOptionalByte: String => Option[Byte] = getOptional(conf.getByte)
187+
188+
/**
189+
* Gets a value of short configuration property.
190+
*
191+
* @return A Some wrapped value of short configuration property if exists, otherwise None.
192+
*/
193+
def getOptionalShort: String => Option[Short] = getOptional(conf.getShort)
194+
195+
/**
196+
* Gets a value of int configuration property.
197+
*
198+
* @return A Some wrapped value of int configuration property if exists, otherwise None.
199+
*/
200+
def getOptionalInt: String => Option[Int] = getOptional(conf.getInt)
201+
202+
/**
203+
* Gets a value of long configuration property.
204+
*
205+
* @return A Some wrapped value of long configuration property if exists, otherwise None.
206+
*/
207+
def getOptionalLong: String => Option[Long] = getOptional(conf.getLong)
208+
209+
/**
210+
* Gets a value of float configuration property.
211+
*
212+
* @return A Some wrapped value of float configuration property if exists, otherwise None.
213+
*/
214+
def getOptionalFloat: String => Option[Float] = getOptional(conf.getFloat)
215+
216+
/**
217+
* Gets a value of double configuration property.
218+
*
219+
* @return A Some wrapped value of double configuration property if exists, otherwise None.
220+
*/
221+
def getOptionalDouble: String => Option[Double] = getOptional(conf.getDouble)
222+
223+
private def getOptional[V](get: String => V)(key: String): Option[V] = {
224+
if (conf.containsKey(key))
225+
Option(get(key))
226+
else
227+
None
228+
}
229+
230+
}
231+
232+
implicit class ConfigurationMapWrapper(val conf: Configuration) extends AnyVal {
233+
234+
/**
235+
* Converts the configuration into map where keys are Strings and values are converted to U type.
236+
* When the value key is not of proper type it throws.
237+
*
238+
* @tparam U type of values in returned map
239+
* @return map representation of the configuration
240+
*/
241+
def toMap[U: TypeTag]: Map[String, U] =
242+
ConfigurationImplicits.toMap[U](conf)
243+
}
244+
245+
/**
246+
* This method needs to be defined outside of the Value class since Scala has issues with TypeTag in Value classes
247+
*/
248+
private def toMap[U: TypeTag](conf: Configuration): Map[String, U] = {
249+
val fun = typeOf[U] match {
250+
case t if t =:= typeOf[String] => (c: Configuration, k: String) => c.getRequiredString(k)
251+
case t if t =:= typeOf[Boolean] => (c: Configuration, k: String) => c.getRequiredBoolean(k)
252+
case t if t =:= typeOf[BigDecimal] => (c: Configuration, k: String) => c.getRequiredBigDecimal(k)
253+
case t if t =:= typeOf[Byte] => (c: Configuration, k: String) => c.getRequiredByte(k)
254+
case t if t =:= typeOf[Short] => (c: Configuration, k: String) => c.getRequiredShort(k)
255+
case t if t =:= typeOf[Int] => (c: Configuration, k: String) => c.getRequiredInt(k)
256+
case t if t =:= typeOf[Long] => (c: Configuration, k: String) => c.getRequiredLong(k)
257+
case t if t =:= typeOf[Float] => (c: Configuration, k: String) => c.getRequiredFloat(k)
258+
case t if t =:= typeOf[Double] => (c: Configuration, k: String) => c.getRequiredDouble(k)
259+
case t if t =:= typeOf[AnyRef] => (c: Configuration, k: String) => c.getProperty(k)
260+
case t if t =:= typeOf[Array[String]] => (c: Configuration, k: String) => c.getRequiredStringArray(k)
261+
case t => throw new UnsupportedOperationException(s"Type $t not supported")
262+
}
263+
264+
conf
265+
.getKeys.asScala
266+
.map(k => k -> fun(conf, k).asInstanceOf[U])
267+
.toMap
268+
}
269+
270+
271+
}

commons/src/main/scala/za/co/absa/spline/common/TimeTracingUtils.scala

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

1717
package za.co.absa.spline.common
1818

19-
import org.apache.commons.lang.time.StopWatch
19+
import org.apache.commons.lang3.time.StopWatch
2020
import org.slf4s.Logging
2121

2222
object TimeTracingUtils extends Logging {

commons/src/main/scala/za/co/absa/spline/common/config/DefaultConfigurationStack.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717
package za.co.absa.spline.common.config
1818

19-
import org.apache.commons.configuration._
20-
import za.co.absa.commons.config.UpperSnakeCaseEnvironmentConfiguration
19+
import org.apache.commons.configuration2.{CompositeConfiguration, EnvironmentConfiguration, JNDIConfiguration, SystemConfiguration}
2120
import za.co.absa.spline.common.config.DefaultConfigurationStack.jndiConfigurationIfAvailable
2221

2322
import java.util
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2025 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package za.co.absa.spline.common.config
18+
19+
import org.apache.commons.configuration2.EnvironmentConfiguration
20+
import org.apache.commons.lang3.StringUtils
21+
import za.co.absa.spline.common.config.UpperSnakeCaseEnvironmentConfiguration.toUpperSnake
22+
23+
class UpperSnakeCaseEnvironmentConfiguration extends EnvironmentConfiguration {
24+
25+
override def getPropertyInternal(key: String): AnyRef = super.getPropertyInternal(toUpperSnake(key))
26+
27+
override def containsKeyInternal(key: String): Boolean = super.containsKeyInternal(toUpperSnake(key))
28+
}
29+
30+
object UpperSnakeCaseEnvironmentConfiguration {
31+
private def toUpperSnake(key: String) = {
32+
key
33+
.split("[\\W_]")
34+
.flatMap(StringUtils.splitByCharacterTypeCamelCase)
35+
.map(_.toUpperCase)
36+
.mkString("_")
37+
}
38+
}

0 commit comments

Comments
 (0)