1616
1717package eu .cloudnetservice .node .impl .service .defaults ;
1818
19- import com .google .common .base .Preconditions ;
20- import com .google .common .primitives .Ints ;
2119import eu .cloudnetservice .driver .event .EventManager ;
2220import eu .cloudnetservice .driver .language .I18n ;
2321import eu .cloudnetservice .driver .service .ServiceConfiguration ;
2422import eu .cloudnetservice .driver .service .ServiceEnvironment ;
2523import eu .cloudnetservice .driver .service .ServiceEnvironmentType ;
2624import eu .cloudnetservice .node .config .Configuration ;
25+ import eu .cloudnetservice .node .event .service .CloudServiceJvmClassPathConstructEvent ;
2726import eu .cloudnetservice .node .event .service .CloudServicePostProcessStartEvent ;
2827import eu .cloudnetservice .node .event .service .CloudServicePreProcessStartEvent ;
2928import eu .cloudnetservice .node .impl .service .InternalCloudServiceManager ;
3029import eu .cloudnetservice .node .impl .service .defaults .log .ProcessServiceLogCache ;
3130import eu .cloudnetservice .node .impl .service .defaults .log .ProcessServiceLogReadScheduler ;
31+ import eu .cloudnetservice .node .impl .service .defaults .wrapper .WrapperFileProvider ;
3232import eu .cloudnetservice .node .impl .tick .DefaultTickLoop ;
3333import eu .cloudnetservice .node .impl .version .ServiceVersionProvider ;
3434import eu .cloudnetservice .node .service .ServiceConfigurationPreparer ;
3535import eu .cloudnetservice .node .service .ServiceConsoleLogCache ;
3636import eu .cloudnetservice .utils .base .StringUtil ;
3737import eu .cloudnetservice .utils .base .io .FileUtil ;
3838import io .vavr .CheckedFunction1 ;
39- import io .vavr .Tuple2 ;
4039import java .io .File ;
4140import java .io .IOException ;
4241import java .nio .charset .StandardCharsets ;
4342import java .nio .file .Files ;
4443import java .nio .file .Path ;
45- import java .nio . file . StandardCopyOption ;
44+ import java .util . ArrayList ;
4645import java .util .Arrays ;
4746import java .util .Collection ;
4847import java .util .LinkedList ;
4948import java .util .List ;
49+ import java .util .Objects ;
5050import java .util .concurrent .TimeUnit ;
5151import java .util .jar .Attributes ;
5252import java .util .jar .JarFile ;
53- import java .util .jar .Manifest ;
54- import java .util .regex .Pattern ;
5553import java .util .stream .Collectors ;
5654import lombok .NonNull ;
5755import org .jetbrains .annotations .Nullable ;
6159public class JVMService extends AbstractService {
6260
6361 protected static final Logger LOGGER = LoggerFactory .getLogger (JVMService .class );
64- protected static final Pattern FILE_NUMBER_PATTERN = Pattern .compile ("(\\ d+).*" );
6562 protected static final Collection <String > DEFAULT_JVM_SYSTEM_PROPERTIES = Arrays .asList (
6663 "--enable-preview" ,
6764 "-Dfile.encoding=UTF-8" ,
@@ -70,7 +67,6 @@ public class JVMService extends AbstractService {
7067 "-Djline.terminal=jline.UnsupportedTerminal" );
7168
7269 protected static final Path LIB_PATH = Path .of ("launcher" , "libs" );
73- protected static final Path WRAPPER_TEMP_FILE = FileUtil .TEMP_DIR .resolve ("caches" ).resolve ("wrapper.jar" );
7470
7571 protected volatile Process process ;
7672
@@ -139,17 +135,20 @@ protected void startProcess() {
139135 }
140136
141137 // get the agent class of the application (if any)
142- var agentClass = applicationInformation ._2 ().mainAttributes ().getValue ("Premain-Class" );
143- if (agentClass == null ) {
144- // some old versions named the agent class 'Launcher-Agent-Class' - try that
145- agentClass = applicationInformation ._2 ().mainAttributes ().getValue ("Launcher-Agent-Class" );
146- }
138+ var agentClass = applicationInformation .mainAttributes ().getValue ("Launcher-Agent-Class" );
147139
148140 // prepare the full wrapper class path
149- var classPath = String .format (
150- "%s%s" ,
151- this .computeWrapperClassPath (wrapperInformation ._1 ()),
152- wrapperInformation ._1 ().toAbsolutePath ());
141+ List <Path > classPathBuilder = new ArrayList <>();
142+ this .computeWrapperClassPath (classPathBuilder , wrapperInformation .path ());
143+ classPathBuilder .add (wrapperInformation .path ());
144+ classPathBuilder .add (applicationInformation .path ());
145+ this .eventManager .callEvent (new CloudServiceJvmClassPathConstructEvent (this , classPathBuilder ));
146+ var classPath = classPathBuilder .stream ()
147+ .map (Path ::toAbsolutePath )
148+ .map (Path ::normalize )
149+ .map (Path ::toString )
150+ .distinct ()
151+ .collect (Collectors .joining (File .pathSeparator ));
153152
154153 // prepare the service startup
155154 List <String > arguments = new LinkedList <>();
@@ -168,33 +167,27 @@ protected void startProcess() {
168167
169168 // override some default configuration options
170169 arguments .addAll (DEFAULT_JVM_SYSTEM_PROPERTIES );
171- arguments .add ("-javaagent:" + wrapperInformation ._1 ().toAbsolutePath ());
170+ arguments .add ("-javaagent:" + wrapperInformation .path ().toAbsolutePath ());
172171 arguments .add ("-Dcloudnet.wrapper.messages.language=" + super .i18n .selectedLanguage ().toLanguageTag ());
173-
174- // fabric specific class path
175- arguments . add ( String . format ( "-Dfabric.systemLibraries=%s" , classPath ));
172+ if ( agentClass != null ) {
173+ arguments . add ( "-Dcloudnet.wrapper.launcher-agent- class=" + agentClass );
174+ }
176175
177176 // set the used host and port as system property
178177 arguments .add ("-Dservice.bind.host=" + this .serviceConfiguration ().hostAddress ());
179178 arguments .add ("-Dservice.bind.port=" + this .serviceConfiguration ().port ());
180179
181- // add the class path and the main class of the wrapper
180+ // add the class path and the main class of the application
182181 arguments .add ("-cp" );
183182 arguments .add (classPath );
184- arguments .add (wrapperInformation ._2 ().getValue ("Main-Class" )); // the main class we want to invoke first
185-
186- // add all internal process parameters (they will be removed by the wrapper before starting the application)
187- arguments .add (applicationInformation ._2 ().mainAttributes ().getValue ("Main-Class" ));
188- arguments .add (String .valueOf (agentClass )); // the agent class might be null
189- arguments .add (applicationInformation ._1 ().toAbsolutePath ().toString ());
190- arguments .add (Boolean .toString (applicationInformation ._2 ().preloadJarContent ()));
183+ arguments .add (applicationInformation .mainAttributes ().getValue (Attributes .Name .MAIN_CLASS ));
191184
192185 // add all process parameters
193186 arguments .addAll (environmentType .defaultProcessArguments ());
194187 arguments .addAll (this .serviceConfiguration ().processConfig ().processParameters ());
195188
196189 // try to start the process like that
197- this .doStartProcess (arguments , wrapperInformation ._1 (), applicationInformation ._1 ());
190+ this .doStartProcess (arguments , wrapperInformation .path (), applicationInformation .path ());
198191 }
199192
200193 @ Override
@@ -287,28 +280,17 @@ protected void doStartProcess(
287280 }
288281 }
289282
290- protected @ Nullable Tuple2 <Path , Attributes > prepareWrapperFile () {
291- // check if the wrapper file is there - unpack it if not
292- if (Files .notExists (WRAPPER_TEMP_FILE )) {
293- FileUtil .createDirectory (WRAPPER_TEMP_FILE .getParent ());
294- try (var stream = JVMService .class .getClassLoader ().getResourceAsStream ("wrapper.jar" )) {
295- // ensure that the wrapper file is there
296- if (stream == null ) {
297- throw new IllegalStateException ("Build-in \" wrapper.jar\" missing, unable to start jvm based services" );
298- }
299- // copy the wrapper file to the output directory
300- Files .copy (stream , WRAPPER_TEMP_FILE , StandardCopyOption .REPLACE_EXISTING );
301- } catch (IOException exception ) {
302- LOGGER .error ("Unable to copy \" wrapper.jar\" to {}" , WRAPPER_TEMP_FILE , exception );
303- }
304- }
305- // read the main class
306- return this .completeJarAttributeInformation (
307- WRAPPER_TEMP_FILE ,
283+
284+ protected @ Nullable JarFileData prepareWrapperFile () {
285+ var wrapperTempPath = WrapperFileProvider .unpackWrapperFile ();
286+ var mainAttributes = this .extractFromJarFile (
287+ wrapperTempPath ,
308288 file -> file .getManifest ().getMainAttributes ());
289+ Objects .requireNonNull (mainAttributes , "Wrapper jar does not contain a manifest" );
290+ return new JarFileData (wrapperTempPath , mainAttributes );
309291 }
310292
311- protected @ Nullable Tuple2 < Path , ApplicationStartupInformation > prepareApplicationFile (
293+ protected @ Nullable JarFileData prepareApplicationFile (
312294 @ NonNull ServiceEnvironmentType environmentType
313295 ) {
314296 // collect all names of environment names
@@ -326,45 +308,29 @@ protected void doStartProcess(
326308 return Files .walk (this .serviceDirectory , 1 )
327309 .filter (path -> {
328310 var filename = path .getFileName ().toString ();
329- // check if the file is a jar file - it must end with '.jar' for that
330311 if (!filename .endsWith (".jar" )) {
331312 return false ;
332313 }
333- // search if any environment is in the name of the file
314+
334315 for (var environment : environments ) {
335316 if (filename .contains (environment )) {
336317 return true ;
337318 }
338319 }
339- // not an application file for the environment
320+
340321 return false ;
341- }).min ((left , right ) -> {
342- // get the first number from the left path
343- var leftMatcher = FILE_NUMBER_PATTERN .matcher (left .getFileName ().toString ());
344- // no match -> neutral
345- if (!leftMatcher .matches ()) {
346- return 0 ;
347- }
322+ })
323+ .map (path -> {
324+ var manifest = this .extractFromJarFile (path , JarFile ::getManifest );
325+ Objects .requireNonNull (manifest , "Application jar does not contain a manifest" );
348326
349- // get the first number from the right patch
350- var rightMatcher = FILE_NUMBER_PATTERN .matcher (right .getFileName ().toString ());
351- // no match -> neutral
352- if (!rightMatcher .matches ()) {
353- return 0 ;
354- }
327+ var mainClass = manifest .getMainAttributes ().getValue ("Main-Class" );
328+ Objects .requireNonNull (mainClass , "Application jar manifest does not contain a Main-Class" );
355329
356- // extract the numbers
357- var leftNumber = Ints .tryParse (leftMatcher .group (1 ));
358- var rightNumber = Ints .tryParse (rightMatcher .group (1 ));
359- // compare both of the numbers
360- return leftNumber == null || rightNumber == null ? 0 : Integer .compare (leftNumber , rightNumber );
330+ return new JarFileData (path , manifest .getMainAttributes ());
361331 })
362- .map (path -> this .completeJarAttributeInformation (
363- path ,
364- file -> new ApplicationStartupInformation (
365- file .getEntry ("META-INF/versions.list" ) != null ,
366- this .validateManifest (file .getManifest ()).getMainAttributes ())
367- )).orElse (null );
332+ .findFirst ()
333+ .orElse (null );
368334 } catch (IOException exception ) {
369335 LOGGER .error (
370336 "Unable to find application file information in {} for environment {}" ,
@@ -375,23 +341,21 @@ protected void doStartProcess(
375341 }
376342 }
377343
378- protected @ Nullable <T > Tuple2 < Path , T > completeJarAttributeInformation (
344+ protected @ Nullable <T > T extractFromJarFile (
379345 @ NonNull Path jarFilePath ,
380346 @ NonNull CheckedFunction1 <JarFile , T > mapper
381347 ) {
382348 // open the file and lookup the main class
383349 try (var jarFile = new JarFile (jarFilePath .toFile ())) {
384- return new Tuple2 <>( jarFilePath , mapper .apply (jarFile ) );
350+ return mapper .apply (jarFile );
385351 } catch (Throwable exception ) {
386352 LOGGER .error ("Unable to open wrapper file at {} for reading: " , jarFilePath , exception );
387353 return null ;
388354 }
389355 }
390356
391- protected @ NonNull String computeWrapperClassPath (@ NonNull Path wrapperPath ) {
392- var builder = new StringBuilder ();
357+ protected void computeWrapperClassPath (@ NonNull Collection <Path > classPath , @ NonNull Path wrapperPath ) {
393358 FileUtil .openZipFile (wrapperPath , fs -> {
394- // get the wrapper cnl file and check if it is available
395359 var wrapperCnl = fs .getPath ("wrapper.cnl" );
396360 if (Files .exists (wrapperCnl )) {
397361 Files .lines (wrapperCnl )
@@ -409,24 +373,12 @@ protected void doStartProcess(
409373 parts [5 ],
410374 parts .length == 8 ? "-" + parts [7 ] : "" );
411375 return LIB_PATH .resolve (path );
412- }).forEach (path -> builder . append ( path . toAbsolutePath ()). append ( File . pathSeparatorChar ) );
376+ }).forEach (classPath :: add );
413377 }
414378 });
415- // contains all paths we need now
416- return builder .toString ();
417- }
418-
419- protected @ NonNull Manifest validateManifest (@ Nullable Manifest manifest ) {
420- // make sure that we have a manifest at all
421- Preconditions .checkNotNull (manifest , "Application jar does not contain a manifest." );
422- // make sure that the manifest at least contains a main class
423- Preconditions .checkNotNull (
424- manifest .getMainAttributes ().getValue ("Main-Class" ),
425- "Application jar manifest does not contain a Main-Class." );
426- return manifest ;
427379 }
428380
429- protected record ApplicationStartupInformation ( boolean preloadJarContent , @ NonNull Attributes mainAttributes ) {
381+ protected record JarFileData ( @ NonNull Path path , @ NonNull Attributes mainAttributes ) {
430382
431383 }
432384}
0 commit comments