11package com .databricks .sdk .core ;
22
3+ import com .databricks .sdk .core .utils .Environment ;
4+ import java .io .File ;
35import java .util .ArrayList ;
46import java .util .Arrays ;
57import java .util .Collections ;
6- import java .util .HashMap ;
78import java .util .List ;
8- import java .util .Map ;
99import java .util .regex .Pattern ;
1010import java .util .stream .Collectors ;
11- import org .slf4j .Logger ;
12- import org .slf4j .LoggerFactory ;
1311
1412public class UserAgent {
15- private static final Logger log = LoggerFactory .getLogger (UserAgent .class );
1613 private static String product = "unknown" ;
1714 private static String productVersion = "0.0.0" ;
1815
@@ -128,9 +125,9 @@ public static String asString() {
128125 segments .add (String .format ("databricks-sdk-java/%s" , version ));
129126 segments .add (String .format ("jvm/%s" , jvmVersion ()));
130127 segments .add (String .format ("os/%s" , osName ()));
131- String ciProvider = cicdProvider ();
132- if (!ciProvider .isEmpty ()) {
133- segments .add (String .format ("ci /%s" , ciProvider ));
128+ String cicdProvider = cicdProvider ();
129+ if (!cicdProvider .isEmpty ()) {
130+ segments .add (String .format ("cicd /%s" , cicdProvider ));
134131 }
135132 // Concurrent iteration over ArrayList must be guarded with synchronized.
136133 synchronized (otherInfo ) {
@@ -142,34 +139,35 @@ public static String asString() {
142139 return segments .stream ().collect (Collectors .joining (" " ));
143140 }
144141
145- // Map of CI/CD providers that are used to detect them.
146- private static final Map < String , List <EnvVar >> PROVIDERS = new HashMap <>();
147-
148- static {
149- PROVIDERS . put ( "github " , Collections .singletonList (new EnvVar ("GITHUB_ACTIONS " , "true" )));
150- PROVIDERS . put ( "gitlab " , Collections .singletonList (new EnvVar ("GITLAB_CI " , "true " )));
151- PROVIDERS . put ( "jenkins " , Collections .singletonList (new EnvVar ("JENKINS_URL " , "" )));
152- PROVIDERS . put ( "azure-devops " , Collections .singletonList (new EnvVar ("TF_BUILD " , "True " )));
153- PROVIDERS . put ( "circle " , Collections .singletonList (new EnvVar ("CIRCLECI " , "true" )));
154- PROVIDERS . put ( "travis " , Collections .singletonList (new EnvVar ("TRAVIS " , "true " )));
155- PROVIDERS . put ( "bitbucket" , Collections . singletonList ( new EnvVar ( "BITBUCKET_BUILD_NUMBER " , "" )));
156- PROVIDERS . put (
157- "google-cloud-build" ,
158- Arrays . asList (
159- new EnvVar ("PROJECT_ID " , "" ),
160- new EnvVar ( "BUILD_ID" , "" ),
161- new EnvVar ("PROJECT_NUMBER " , "" ),
162- new EnvVar ("LOCATION " , "" )));
163- PROVIDERS . put (
164- "aws-code-build" , Collections . singletonList ( new EnvVar ( "CODEBUILD_BUILD_ARN" , "" )));
165- PROVIDERS . put ( "tf-cloud" , Collections . singletonList ( new EnvVar ( "TFC_RUN_ID" , "" )));
166- }
167-
168- // This is a static private variable to store the CI/CD provider.
169- // This is thread-safe because static initializers are executed
170- // in a thread-safe manner by the Java ClassLoader.
171- private static final String cicdProvider = lookupCiCdProvider () ;
142+ // List of CI/CD providers and their environment variables for detection
143+ private static List <CicdProvider > listCiCdProviders () {
144+ return Arrays . asList (
145+ new CicdProvider ( "github" , Collections . singletonList ( new EnvVar ( "GITHUB_ACTIONS" , "true" ))),
146+ new CicdProvider ( "gitlab " , Collections .singletonList (new EnvVar ("GITLAB_CI " , "true" ))),
147+ new CicdProvider ( "jenkins " , Collections .singletonList (new EnvVar ("JENKINS_URL " , "" ))),
148+ new CicdProvider ( "azure-devops " , Collections .singletonList (new EnvVar ("TF_BUILD " , "True " ))),
149+ new CicdProvider ( "circle " , Collections .singletonList (new EnvVar ("CIRCLECI " , "true " ))),
150+ new CicdProvider ( "travis " , Collections .singletonList (new EnvVar ("TRAVIS " , "true" ))),
151+ new CicdProvider ( "bitbucket " , Collections .singletonList (new EnvVar ("BITBUCKET_BUILD_NUMBER " , "" ))),
152+ new CicdProvider ( "google-cloud-build " , Arrays . asList (
153+ new EnvVar ( "PROJECT_ID" , "" ),
154+ new EnvVar ( "BUILD_ID" , "" ) ,
155+ new EnvVar ( "PROJECT_NUMBER" , "" ),
156+ new EnvVar ("LOCATION " , "" )
157+ ) ),
158+ new CicdProvider ( "aws-code-build" , Collections . singletonList ( new EnvVar ("CODEBUILD_BUILD_ARN " , "" )) ),
159+ new CicdProvider ( "tf-cloud" , Collections . singletonList ( new EnvVar ("TFC_RUN_ID " , "" )))
160+ );
161+ }
162+
163+ // Volatile fields to ensure thread-safe lazy initialization
164+ // The 'volatile' keyword ensures that changes to these variables
165+ // are immediately visible to all threads. It prevents instruction
166+ // reordering by the compiler.
167+ protected static volatile String cicdProvider = null ;
168+ protected static volatile Environment env = null ;
172169
170+ // Represents an environment variable with its name and expected value
173171 private static class EnvVar {
174172 private final String name ;
175173 private final String expectedValue ;
@@ -178,23 +176,62 @@ public EnvVar(String name, String expectedValue) {
178176 this .name = name ;
179177 this .expectedValue = expectedValue ;
180178 }
179+ }
180+
181+ // Represents a CI/CD provider with its name and associated environment variables
182+ private static class CicdProvider {
183+ private final String name ;
184+ private final List <EnvVar > envVars ;
181185
182- public boolean detect () {
183- String value = System .getenv (name );
184- return value != null && (expectedValue .isEmpty () || value .equals (expectedValue ));
186+ public CicdProvider (String name , List <EnvVar > envVars ) {
187+ this .name = name ;
188+ this .envVars = envVars ;
189+ }
190+
191+ public boolean detect (Environment env ) {
192+ for (EnvVar envVar : envVars ) {
193+ String value = env .get (envVar .name );
194+ if (value == null ) {
195+ return false ;
196+ }
197+ if (!envVar .expectedValue .isEmpty () && !value .equals (envVar .expectedValue )) {
198+ return false ;
199+ }
200+ }
201+ return true ;
185202 }
186203 }
187204
188- private static String lookupCiCdProvider () {
189- for (Map .Entry <String , List <EnvVar >> entry : PROVIDERS .entrySet ()) {
190- if (entry .getValue ().stream ().allMatch (EnvVar ::detect )) {
191- return entry .getKey ();
205+ // Looks up the active CI/CD provider based on environment variables
206+ private static String lookupCiCdProvider (Environment env ) {
207+ for (CicdProvider provider : listCiCdProviders ()) {
208+ if (provider .detect (env )) {
209+ return provider .name ;
192210 }
193211 }
194212 return "" ;
195213 }
196214
197- public static String cicdProvider () {
215+ // Thread-safe lazy initialization of CI/CD provider detection
216+ private static String cicdProvider () {
217+ // First check (not synchronized) to avoid unnecessary synchronization
218+ if (cicdProvider == null ) {
219+ // Synchronize only if cicdProvider is null
220+ synchronized (UserAgent .class ) {
221+ // Second check (synchronized) to ensure only one thread initializes
222+ // This is necessary because multiple threads might have passed the first check
223+ if (cicdProvider == null ) {
224+ cicdProvider = lookupCiCdProvider (env ());
225+ }
226+ }
227+ }
198228 return cicdProvider ;
199229 }
230+
231+ protected static Environment env () {
232+ if (env == null ) {
233+ env = new Environment (System .getenv (), System .getenv ("PATH" ).split (File .pathSeparator ), System .getProperty ("os.name" ));
234+ }
235+ return env ;
236+ }
200237}
0 commit comments