1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.catalina.startup;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.net.MalformedURLException;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import org.apache.catalina.Globals;
31 import org.apache.catalina.security.SecurityClassLoad;
32 import org.apache.catalina.startup.ClassLoaderFactory.Repository;
33 import org.apache.catalina.startup.ClassLoaderFactory.RepositoryType;
34 import org.apache.juli.logging.Log;
35 import org.apache.juli.logging.LogFactory;
36
37 /**
38  * Bootstrap loader for Catalina.  This application constructs a class loader
39  * for use in loading the Catalina internal classes (by accumulating all of the
40  * JAR files found in the "server" directory under "catalina.home"), and
41  * starts the regular execution of the container.  The purpose of this
42  * roundabout approach is to keep the Catalina internal classes (and any
43  * other classes they depend on, such as an XML parser) out of the system
44  * class path and therefore not visible to application level classes.
45  *
46  * @author Craig R. McClanahan
47  * @author Remy Maucherat
48  */

49 public final class Bootstrap {
50
51     private static final Log log = LogFactory.getLog(Bootstrap.class);
52
53     /**
54      * Daemon object used by main.
55      */

56     private static final Object daemonLock = new Object();
57     private static volatile Bootstrap daemon = null;
58
59     private static final File catalinaBaseFile;
60     private static final File catalinaHomeFile;
61
62     private static final Pattern PATH_PATTERN = Pattern.compile("(\".*?\")|(([^,])*)");
63
64     static {
65         // Will always be non-null
66         String userDir = System.getProperty("user.dir");
67
68         // Home first
69         String home = System.getProperty(Globals.CATALINA_HOME_PROP);
70         File homeFile = null;
71
72         if (home != null) {
73             File f = new File(home);
74             try {
75                 homeFile = f.getCanonicalFile();
76             } catch (IOException ioe) {
77                 homeFile = f.getAbsoluteFile();
78             }
79         }
80
81         if (homeFile == null) {
82             // First fall-back. See if current directory is a bin directory
83             // in a normal Tomcat install
84             File bootstrapJar = new File(userDir, "bootstrap.jar");
85
86             if (bootstrapJar.exists()) {
87                 File f = new File(userDir, "..");
88                 try {
89                     homeFile = f.getCanonicalFile();
90                 } catch (IOException ioe) {
91                     homeFile = f.getAbsoluteFile();
92                 }
93             }
94         }
95
96         if (homeFile == null) {
97             // Second fall-back. Use current directory
98             File f = new File(userDir);
99             try {
100                 homeFile = f.getCanonicalFile();
101             } catch (IOException ioe) {
102                 homeFile = f.getAbsoluteFile();
103             }
104         }
105
106         catalinaHomeFile = homeFile;
107         System.setProperty(
108                 Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
109
110         // Then base
111         String base = System.getProperty(Globals.CATALINA_BASE_PROP);
112         if (base == null) {
113             catalinaBaseFile = catalinaHomeFile;
114         } else {
115             File baseFile = new File(base);
116             try {
117                 baseFile = baseFile.getCanonicalFile();
118             } catch (IOException ioe) {
119                 baseFile = baseFile.getAbsoluteFile();
120             }
121             catalinaBaseFile = baseFile;
122         }
123         System.setProperty(
124                 Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
125     }
126
127     // -------------------------------------------------------------- Variables
128
129
130     /**
131      * Daemon reference.
132      */

133     private Object catalinaDaemon = null;
134
135     ClassLoader commonLoader = null;
136     ClassLoader catalinaLoader = null;
137     ClassLoader sharedLoader = null;
138
139
140     // -------------------------------------------------------- Private Methods
141
142
143     private void initClassLoaders() {
144         try {
145             commonLoader = createClassLoader("common"null);
146             if (commonLoader == null) {
147                 // no config file, default to this loader - we might be in a 'single' env.
148                 commonLoader = this.getClass().getClassLoader();
149             }
150             catalinaLoader = createClassLoader("server", commonLoader);
151             sharedLoader = createClassLoader("shared", commonLoader);
152         } catch (Throwable t) {
153             handleThrowable(t);
154             log.error("Class loader creation threw exception", t);
155             System.exit(1);
156         }
157     }
158
159
160     private ClassLoader createClassLoader(String name, ClassLoader parent)
161         throws Exception {
162
163         String value = CatalinaProperties.getProperty(name + ".loader");
164         if ((value == null) || (value.equals("")))
165             return parent;
166
167         value = replace(value);
168
169         List<Repository> repositories = new ArrayList<>();
170
171         String[] repositoryPaths = getPaths(value);
172
173         for (String repository : repositoryPaths) {
174             // Check for a JAR URL repository
175             try {
176                 @SuppressWarnings("unused")
177                 URL url = new URL(repository);
178                 repositories.add(new Repository(repository, RepositoryType.URL));
179                 continue;
180             } catch (MalformedURLException e) {
181                 // Ignore
182             }
183
184             // Local repository
185             if (repository.endsWith("*.jar")) {
186                 repository = repository.substring
187                     (0, repository.length() - "*.jar".length());
188                 repositories.add(new Repository(repository, RepositoryType.GLOB));
189             } else if (repository.endsWith(".jar")) {
190                 repositories.add(new Repository(repository, RepositoryType.JAR));
191             } else {
192                 repositories.add(new Repository(repository, RepositoryType.DIR));
193             }
194         }
195
196         return ClassLoaderFactory.createClassLoader(repositories, parent);
197     }
198
199
200     /**
201      * System property replacement in the given string.
202      *
203      * @param str The original string
204      * @return the modified string
205      */

206     protected String replace(String str) {
207         // Implementation is copied from ClassLoaderLogManager.replace(),
208         // but added special processing for catalina.home and catalina.base.
209         String result = str;
210         int pos_start = str.indexOf("${");
211         if (pos_start >= 0) {
212             StringBuilder builder = new StringBuilder();
213             int pos_end = -1;
214             while (pos_start >= 0) {
215                 builder.append(str, pos_end + 1, pos_start);
216                 pos_end = str.indexOf('}', pos_start + 2);
217                 if (pos_end < 0) {
218                     pos_end = pos_start - 1;
219                     break;
220                 }
221                 String propName = str.substring(pos_start + 2, pos_end);
222                 String replacement;
223                 if (propName.length() == 0) {
224                     replacement = null;
225                 } else if (Globals.CATALINA_HOME_PROP.equals(propName)) {
226                     replacement = getCatalinaHome();
227                 } else if (Globals.CATALINA_BASE_PROP.equals(propName)) {
228                     replacement = getCatalinaBase();
229                 } else {
230                     replacement = System.getProperty(propName);
231                 }
232                 if (replacement != null) {
233                     builder.append(replacement);
234                 } else {
235                     builder.append(str, pos_start, pos_end + 1);
236                 }
237                 pos_start = str.indexOf("${", pos_end + 1);
238             }
239             builder.append(str, pos_end + 1, str.length());
240             result = builder.toString();
241         }
242         return result;
243     }
244
245
246     /**
247      * Initialize daemon.
248      * @throws Exception Fatal initialization error
249      */

250     public void init() throws Exception {
251
252         initClassLoaders();
253
254         Thread.currentThread().setContextClassLoader(catalinaLoader);
255
256         SecurityClassLoad.securityClassLoad(catalinaLoader);
257
258         // Load our startup class and call its process() method
259         if (log.isDebugEnabled())
260             log.debug("Loading startup class");
261         Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
262         Object startupInstance = startupClass.getConstructor().newInstance();
263
264         // Set the shared extensions class loader
265         if (log.isDebugEnabled())
266             log.debug("Setting startup class properties");
267         String methodName = "setParentClassLoader";
268         Class<?> paramTypes[] = new Class[1];
269         paramTypes[0] = Class.forName("java.lang.ClassLoader");
270         Object paramValues[] = new Object[1];
271         paramValues[0] = sharedLoader;
272         Method method =
273             startupInstance.getClass().getMethod(methodName, paramTypes);
274         method.invoke(startupInstance, paramValues);
275
276         catalinaDaemon = startupInstance;
277     }
278
279
280     /**
281      * Load daemon.
282      */

283     private void load(String[] arguments) throws Exception {
284
285         // Call the load() method
286         String methodName = "load";
287         Object param[];
288         Class<?> paramTypes[];
289         if (arguments==null || arguments.length==0) {
290             paramTypes = null;
291             param = null;
292         } else {
293             paramTypes = new Class[1];
294             paramTypes[0] = arguments.getClass();
295             param = new Object[1];
296             param[0] = arguments;
297         }
298         Method method =
299             catalinaDaemon.getClass().getMethod(methodName, paramTypes);
300         if (log.isDebugEnabled()) {
301             log.debug("Calling startup class " + method);
302         }
303         method.invoke(catalinaDaemon, param);
304     }
305
306
307     /**
308      * getServer() for configtest
309      */

310     private Object getServer() throws Exception {
311
312         String methodName = "getServer";
313         Method method = catalinaDaemon.getClass().getMethod(methodName);
314         return method.invoke(catalinaDaemon);
315     }
316
317
318     // ----------------------------------------------------------- Main Program
319
320
321     /**
322      * Load the Catalina daemon.
323      * @param arguments Initialization arguments
324      * @throws Exception Fatal initialization error
325      */

326     public void init(String[] arguments) throws Exception {
327
328         init();
329         load(arguments);
330     }
331
332
333     /**
334      * Start the Catalina daemon.
335      * @throws Exception Fatal start error
336      */

337     public void start() throws Exception {
338         if (catalinaDaemon == null) {
339             init();
340         }
341
342         Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
343         method.invoke(catalinaDaemon, (Object [])null);
344     }
345
346
347     /**
348      * Stop the Catalina Daemon.
349      * @throws Exception Fatal stop error
350      */

351     public void stop() throws Exception {
352         Method method = catalinaDaemon.getClass().getMethod("stop", (Class []) null);
353         method.invoke(catalinaDaemon, (Object []) null);
354     }
355
356
357     /**
358      * Stop the standalone server.
359      * @throws Exception Fatal stop error
360      */

361     public void stopServer() throws Exception {
362
363         Method method =
364             catalinaDaemon.getClass().getMethod("stopServer", (Class []) null);
365         method.invoke(catalinaDaemon, (Object []) null);
366     }
367
368
369    /**
370      * Stop the standalone server.
371      * @param arguments Command line arguments
372      * @throws Exception Fatal stop error
373      */

374     public void stopServer(String[] arguments) throws Exception {
375
376         Object param[];
377         Class<?> paramTypes[];
378         if (arguments == null || arguments.length == 0) {
379             paramTypes = null;
380             param = null;
381         } else {
382             paramTypes = new Class[1];
383             paramTypes[0] = arguments.getClass();
384             param = new Object[1];
385             param[0] = arguments;
386         }
387         Method method =
388             catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
389         method.invoke(catalinaDaemon, param);
390     }
391
392
393     /**
394      * Set flag.
395      * @param await <code>true</code> if the daemon should block
396      * @throws Exception Reflection error
397      */

398     public void setAwait(boolean await)
399         throws Exception {
400
401         Class<?> paramTypes[] = new Class[1];
402         paramTypes[0] = Boolean.TYPE;
403         Object paramValues[] = new Object[1];
404         paramValues[0] = Boolean.valueOf(await);
405         Method method =
406             catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
407         method.invoke(catalinaDaemon, paramValues);
408     }
409
410     public boolean getAwait() throws Exception {
411         Class<?> paramTypes[] = new Class[0];
412         Object paramValues[] = new Object[0];
413         Method method =
414             catalinaDaemon.getClass().getMethod("getAwait", paramTypes);
415         Boolean b=(Boolean)method.invoke(catalinaDaemon, paramValues);
416         return b.booleanValue();
417     }
418
419
420     /**
421      * Destroy the Catalina Daemon.
422      */

423     public void destroy() {
424
425         // FIXME
426
427     }
428
429
430     /**
431      * Main method and entry point when starting Tomcat via the provided
432      * scripts.
433      *
434      * @param args Command line arguments to be processed
435      */

436     public static void main(String args[]) {
437
438         synchronized (daemonLock) {
439             if (daemon == null) {
440                 // Don't set daemon until init() has completed
441                 Bootstrap bootstrap = new Bootstrap();
442                 try {
443                     bootstrap.init();
444                 } catch (Throwable t) {
445                     handleThrowable(t);
446                     t.printStackTrace();
447                     return;
448                 }
449                 daemon = bootstrap;
450             } else {
451                 // When running as a service the call to stop will be on a new
452                 // thread so make sure the correct class loader is used to
453                 // prevent a range of class not found exceptions.
454                 Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
455             }
456         }
457
458         try {
459             String command = "start";
460             if (args.length > 0) {
461                 command = args[args.length - 1];
462             }
463
464             if (command.equals("startd")) {
465                 args[args.length - 1] = "start";
466                 daemon.load(args);
467                 daemon.start();
468             } else if (command.equals("stopd")) {
469                 args[args.length - 1] = "stop";
470                 daemon.stop();
471             } else if (command.equals("start")) {
472                 daemon.setAwait(true);
473                 daemon.load(args);
474                 daemon.start();
475                 if (null == daemon.getServer()) {
476                     System.exit(1);
477                 }
478             } else if (command.equals("stop")) {
479                 daemon.stopServer(args);
480             } else if (command.equals("configtest")) {
481                 daemon.load(args);
482                 if (null == daemon.getServer()) {
483                     System.exit(1);
484                 }
485                 System.exit(0);
486             } else {
487                 log.warn("Bootstrap: command \"" + command + "\" does not exist.");
488             }
489         } catch (Throwable t) {
490             // Unwrap the Exception for clearer error reporting
491             if (t instanceof InvocationTargetException &&
492                     t.getCause() != null) {
493                 t = t.getCause();
494             }
495             handleThrowable(t);
496             t.printStackTrace();
497             System.exit(1);
498         }
499     }
500
501
502     /**
503      * Obtain the name of configured home (binary) directory. Note that home and
504      * base may be the same (and are by default).
505      * @return the catalina home
506      */

507     public static String getCatalinaHome() {
508         return catalinaHomeFile.getPath();
509     }
510
511
512     /**
513      * Obtain the name of the configured base (instance) directory. Note that
514      * home and base may be the same (and are by default). If this is not set
515      * the value returned by {@link #getCatalinaHome()} will be used.
516      * @return the catalina base
517      */

518     public static String getCatalinaBase() {
519         return catalinaBaseFile.getPath();
520     }
521
522
523     /**
524      * Obtain the configured home (binary) directory. Note that home and
525      * base may be the same (and are by default).
526      * @return the catalina home as a file
527      */

528     public static File getCatalinaHomeFile() {
529         return catalinaHomeFile;
530     }
531
532
533     /**
534      * Obtain the configured base (instance) directory. Note that
535      * home and base may be the same (and are by default). If this is not set
536      * the value returned by {@link #getCatalinaHomeFile()} will be used.
537      * @return the catalina base as a file
538      */

539     public static File getCatalinaBaseFile() {
540         return catalinaBaseFile;
541     }
542
543
544     // Copied from ExceptionUtils since that class is not visible during start
545     private static void handleThrowable(Throwable t) {
546         if (t instanceof ThreadDeath) {
547             throw (ThreadDeath) t;
548         }
549         if (t instanceof VirtualMachineError) {
550             throw (VirtualMachineError) t;
551         }
552         // All other instances of Throwable will be silently swallowed
553     }
554
555
556     // Protected for unit testing
557     protected static String[] getPaths(String value) {
558
559         List<String> result = new ArrayList<>();
560         Matcher matcher = PATH_PATTERN.matcher(value);
561
562         while (matcher.find()) {
563             String path = value.substring(matcher.start(), matcher.end());
564
565             path = path.trim();
566             if (path.length() == 0) {
567                 continue;
568             }
569
570             char first = path.charAt(0);
571             char last = path.charAt(path.length() - 1);
572
573             if (first == '"' && last == '"' && path.length() > 1) {
574                 path = path.substring(1, path.length() - 1);
575                 path = path.trim();
576                 if (path.length() == 0) {
577                     continue;
578                 }
579             } else if (path.contains("\"")) {
580                 // Unbalanced quotes
581                 // Too early to use standard i18n support. The class path hasn't
582                 // been configured.
583                 throw new IllegalArgumentException(
584                         "The double quote [\"] character only be used to quote paths. It must " +
585                         "not appear in a path. This loader path is not valid: [" + value + "]");
586             } else {
587                 // Not quoted - NO-OP
588             }
589
590             result.add(path);
591         }
592
593         return result.toArray(new String[result.size()]);
594     }
595 }
596