11package org .mangorage .bootstrap ;
22
3- import com .google .gson .Gson ;
43import org .mangorage .bootstrap .api .launch .ILaunchTarget ;
54import org .mangorage .bootstrap .internal .util .Util ;
65
1312import java .util .Map ;
1413import java .util .ServiceLoader ;
1514import java .util .Set ;
16-
15+ import java .util .logging .Logger ;
16+ import java .util .logging .Level ;
17+
18+ /**
19+ * Bootstrap orchestrator for modular applications.
20+ *
21+ * <p>This class manages the startup sequence by:
22+ * <ol>
23+ * <li>Parsing and validating command-line arguments</li>
24+ * <li>Constructing module layers with proper hierarchy</li>
25+ * <li>Discovering launch targets via ServiceLoader</li>
26+ * <li>Delegating execution to the selected target</li>
27+ * </ol>
28+ *
29+ * <p><strong>Usage:</strong> {@code java -m org.mangorage.bootstrap --launchTarget <targetId>}
30+ *
31+ * @since 1.0.84
32+ * @see ILaunchTarget
33+ */
1734public final class Bootstrap {
1835
19- private static final Gson GSON = new Gson ();
20-
36+ private static final Logger LOGGER = Logger .getLogger (Bootstrap .class .getName ());
37+ private static final String LAUNCH_TARGET_ARG = "--launchTarget" ;
38+ private static final String DEFAULT_LAUNCH_PATH = "launch" ;
39+
40+ /**
41+ * Main entry point for the bootstrap framework.
42+ *
43+ * @param args Command line arguments. Must include --launchTarget followed by target ID.
44+ * @throws IllegalArgumentException if arguments are invalid
45+ * @throws IllegalStateException if launch target cannot be found or executed
46+ */
2147 public static void main (String [] args ) throws Throwable {
22- if (!(args .length >= 2 )) {
23- throw new IllegalStateException ("Need to define a launchTarget, --launchTarget mangobot" );
48+ LOGGER .info ("Starting MangoBotBootstrap framework" );
49+
50+ validateArguments (args );
51+
52+ final String launchTarget = args [1 ];
53+ validateLaunchTarget (launchTarget );
54+
55+ LOGGER .info ("Initializing module layers for launch target: " + launchTarget );
56+
57+ ModuleLayer parent = getParentModuleLayer ();
58+ Path launchPath = Path .of (DEFAULT_LAUNCH_PATH );
59+
60+ final ModuleLayer moduleLayer = createLaunchModuleLayer (parent , launchPath );
61+ final Map <String , ILaunchTarget > launchTargetMap = discoverLaunchTargets (moduleLayer );
62+
63+ if (!launchTargetMap .containsKey (launchTarget )) {
64+ throw new IllegalStateException (
65+ String .format ("Launch target '%s' not found. Available targets: %s" ,
66+ launchTarget , launchTargetMap .keySet ()));
67+ }
68+
69+ LOGGER .info ("Loading BootstrapLifecycle hooks" );
70+ final var lifecycleHooks = ServiceLoader .load (moduleLayer , org .mangorage .bootstrap .api .lifecycle .IBootstrapLifecycle .class )
71+ .stream ()
72+ .map (ServiceLoader .Provider ::get )
73+ .toList ();
74+
75+ LOGGER .info ("Launching target: " + launchTarget );
76+
77+ try {
78+ launchTargetMap .get (launchTarget ).launch (moduleLayer , parent , args );
79+ } catch (Throwable t ) {
80+ LOGGER .log (Level .SEVERE , "Error during launch target execution: " + launchTarget , t );
81+ lifecycleHooks .forEach (hook -> hook .onError (t , moduleLayer ));
82+ throw t ;
83+ }
84+
85+ LOGGER .info ("Bootstrap completed successfully" );
86+ }
87+
88+ /**
89+ * Validates command-line arguments format and content.
90+ */
91+ private static void validateArguments (String [] args ) {
92+ if (args .length < 2 ) {
93+ throw new IllegalArgumentException (
94+ "Missing required arguments. Usage: --launchTarget <targetId>" );
95+ }
96+
97+ if (!LAUNCH_TARGET_ARG .equals (args [0 ])) {
98+ throw new IllegalArgumentException (
99+ String .format ("First argument must be '%s', got: %s" , LAUNCH_TARGET_ARG , args [0 ]));
24100 }
101+ }
25102
26- if (!args [0 ].equals ("--launchTarget" )) {
27- throw new IllegalStateException ("Need to have --launchTarget be defined first..." );
103+ /**
104+ * Validates the launch target identifier format.
105+ */
106+ private static void validateLaunchTarget (String target ) {
107+ if (target == null || target .trim ().isEmpty ()) {
108+ throw new IllegalArgumentException ("Launch target cannot be null or empty" );
28109 }
29110
30- final var launchTarget = args [1 ];
111+ if (!target .matches ("[a-zA-Z0-9._-]+" )) {
112+ throw new IllegalArgumentException (
113+ String .format ("Invalid launch target format: '%s'. Must contain only letters, numbers, dots, underscores, and hyphens" , target ));
114+ }
115+ }
31116
117+ /**
118+ * Determines the appropriate parent module layer.
119+ */
120+ private static ModuleLayer getParentModuleLayer () {
32121 ModuleLayer parent = null ;
33122
34123 if (Bootstrap .class .getModule () != null ) {
35124 parent = Bootstrap .class .getModule ().getLayer ();
36125 }
37126
38- if (parent == null )
39- parent = ModuleLayer .boot ();
40-
41- // Where additional launch targets can be defined...
42- Path launchPath = Path .of (
43- "launch"
44- );
45-
46- final var moduleCfg = Configuration
47- .resolveAndBind (
48- ModuleFinder .of (
49- launchPath
50- ),
51- List .of (
52- parent .configuration ()
53- ),
54- ModuleFinder .of (),
55- Files .exists (launchPath ) ?
56- Util .getModuleNames (
57- launchPath
58- ) : Set .of ()
59- );
60-
61- final var moduleLayerController = ModuleLayer .defineModulesWithOneLoader (
62- moduleCfg ,
63- List .of (parent ),
64- Thread .currentThread ().getContextClassLoader ()
65- );
66- final var moduleLayer = moduleLayerController .layer ();
127+ return parent != null ? parent : ModuleLayer .boot ();
128+ }
67129
68- final Map <String , ILaunchTarget > launchTargetMap = new HashMap <>();
130+ /**
131+ * Creates the launch module layer from the specified path.
132+ */
133+ private static ModuleLayer createLaunchModuleLayer (ModuleLayer parent , Path launchPath ) {
134+ try {
135+ final Configuration moduleCfg = Configuration .resolveAndBind (
136+ ModuleFinder .of (launchPath ),
137+ List .of (parent .configuration ()),
138+ ModuleFinder .of (),
139+ Files .exists (launchPath ) ? Util .getModuleNames (launchPath ) : Set .of ()
140+ );
141+
142+ final ModuleLayer .Controller moduleLayerController = ModuleLayer .defineModulesWithOneLoader (
143+ moduleCfg ,
144+ List .of (parent ),
145+ Thread .currentThread ().getContextClassLoader ()
146+ );
147+
148+ LOGGER .fine ("Successfully created module layer with " + moduleCfg .modules ().size () + " modules" );
149+ return moduleLayerController .layer ();
150+
151+ } catch (Exception e ) {
152+ LOGGER .log (Level .SEVERE , "Failed to create module layer from path: " + launchPath , e );
153+ throw new IllegalStateException ("Module layer creation failed" , e );
154+ }
155+ }
69156
70- ServiceLoader .load (moduleLayer , ILaunchTarget .class )
71- .stream ()
72- .forEach (provider -> {
73- final var target = provider .get ();
74- launchTargetMap .put (target .getId (), target );
75- });
157+ /**
158+ * Discovers all available launch targets in the module layer.
159+ */
160+ private static Map <String , ILaunchTarget > discoverLaunchTargets (ModuleLayer moduleLayer ) {
161+ final Map <String , ILaunchTarget > launchTargetMap = new HashMap <>();
76162
77- if (!launchTargetMap .containsKey (launchTarget )) {
78- throw new IllegalStateException ("Cant find launch target '%s'" .formatted (launchTarget ));
163+ try {
164+ ServiceLoader .load (moduleLayer , ILaunchTarget .class )
165+ .stream ()
166+ .forEach (provider -> {
167+ try {
168+ final ILaunchTarget target = provider .get ();
169+ final String targetId = target .getId ();
170+
171+ if (targetId == null || targetId .trim ().isEmpty ()) {
172+ LOGGER .warning ("Ignoring launch target with null or empty ID from provider: " + provider .type ());
173+ return ;
174+ }
175+
176+ if (launchTargetMap .containsKey (targetId )) {
177+ LOGGER .warning ("Duplicate launch target ID detected: " + targetId + ". Using first occurrence." );
178+ return ;
179+ }
180+
181+ launchTargetMap .put (targetId , target );
182+ LOGGER .fine ("Discovered launch target: " + targetId + " (" + provider .type () + ")" );
183+
184+ } catch (Exception e ) {
185+ LOGGER .log (Level .WARNING , "Failed to load launch target provider: " + provider .type (), e );
186+ }
187+ });
188+
189+ } catch (Exception e ) {
190+ LOGGER .log (Level .SEVERE , "Failed to discover launch targets" , e );
191+ throw new IllegalStateException ("Launch target discovery failed" , e );
79192 }
80193
81- launchTargetMap .get (launchTarget ).launch (moduleLayer , parent , args );
194+ LOGGER .info ("Discovered " + launchTargetMap .size () + " launch targets: " + launchTargetMap .keySet ());
195+ return launchTargetMap ;
82196 }
83197}
0 commit comments