001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     * 
010     *      http://www.apache.org/licenses/LICENSE-2.0
011     * 
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.oozie.action.hadoop;
019    
020    import java.io.BufferedReader;
021    import java.io.File;
022    import java.io.FileReader;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.InputStreamReader;
026    import java.io.OutputStream;
027    import java.io.OutputStreamWriter;
028    import java.io.PrintWriter;
029    import java.io.StringWriter;
030    import java.io.Writer;
031    import java.lang.reflect.InvocationTargetException;
032    import java.lang.reflect.Method;
033    import java.security.Permission;
034    import java.text.MessageFormat;
035    import java.util.Properties;
036    import java.util.StringTokenizer;
037    import java.util.concurrent.ScheduledThreadPoolExecutor;
038    import java.util.concurrent.TimeUnit;
039    
040    import org.apache.hadoop.conf.Configuration;
041    import org.apache.hadoop.fs.FileSystem;
042    import org.apache.hadoop.fs.Path;
043    import org.apache.hadoop.mapred.Counters;
044    import org.apache.hadoop.mapred.JobConf;
045    import org.apache.hadoop.mapred.Mapper;
046    import org.apache.hadoop.mapred.OutputCollector;
047    import org.apache.hadoop.mapred.Reporter;
048    import org.apache.hadoop.mapred.RunningJob;
049    import org.apache.oozie.service.HadoopAccessorException;
050    import org.apache.oozie.service.HadoopAccessorService;
051    import org.apache.oozie.service.Services;
052    import org.apache.oozie.util.XLog;
053    
054    public class LauncherMapper<K1, V1, K2, V2> implements Mapper<K1, V1, K2, V2>, Runnable {
055    
056        public static final String CONF_OOZIE_ACTION_MAIN_CLASS = "oozie.launcher.action.main.class";
057    
058        private static final String CONF_OOZIE_ACTION_MAIN_ARG_COUNT = "oozie.action.main.arg.count";
059        private static final String CONF_OOZIE_ACTION_MAIN_ARG_PREFIX = "oozie.action.main.arg.";
060        private static final String CONF_OOZIE_ACTION_MAX_OUTPUT_DATA = "oozie.action.max.output.data";
061    
062        private static final String COUNTER_GROUP = "oozie.launcher";
063        private static final String COUNTER_DO_ID_SWAP = "oozie.do.id.swap";
064        private static final String COUNTER_OUTPUT_DATA = "oozie.output.data";
065        private static final String COUNTER_LAUNCHER_ERROR = "oozie.launcher.error";
066    
067        private static final String OOZIE_JOB_ID = "oozie.job.id";
068        private static final String OOZIE_ACTION_ID = "oozie.action.id";
069    
070        private static final String OOZIE_ACTION_DIR_PATH = "oozie.action.dir.path";
071        private static final String OOZIE_ACTION_RECOVERY_ID = "oozie.action.recovery.id";
072    
073        static final String ACTION_CONF_XML = "action.xml";
074        private static final String ACTION_OUTPUT_PROPS = "output.properties";
075        private static final String ACTION_NEW_ID_PROPS = "newId.properties";
076        private static final String ACTION_ERROR_PROPS = "error.properties";
077    
078        private void setRecoveryId(Configuration launcherConf, Path actionDir, String recoveryId) throws LauncherException {
079            try {
080                FileSystem fs = FileSystem.get(launcherConf);
081                String jobId = launcherConf.get("mapred.job.id");
082                Path path = new Path(actionDir, recoveryId);
083                if (!fs.exists(path)) {
084                    try {
085                        Writer writer = new OutputStreamWriter(fs.create(path));
086                        writer.write(jobId);
087                        writer.close();
088                    }
089                    catch (IOException ex) {
090                        failLauncher(0, "IO error", ex);
091                    }
092                }
093                else {
094                    InputStream is = fs.open(path);
095                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
096                    String id = reader.readLine();
097                    reader.close();
098                    if (!jobId.equals(id)) {
099                        failLauncher(0, MessageFormat.format(
100                                "Hadoop job Id mismatch, action file [{0}] declares Id [{1}] current Id [{2}]", path, id,
101                                jobId), null);
102                    }
103    
104                }
105            }
106            catch (IOException ex) {
107                failLauncher(0, "IO error", ex);
108            }
109        }
110    
111        /**
112         * @param launcherConf
113         * @param actionDir
114         * @param recoveryId
115         * @return
116         * @throws HadoopAccessorException
117         * @throws IOException
118         */
119        public static String getRecoveryId(Configuration launcherConf, Path actionDir, String recoveryId)
120                throws HadoopAccessorException, IOException {
121            String jobId = null;
122            Path recoveryFile = new Path(actionDir, recoveryId);
123            //FileSystem fs = FileSystem.get(launcherConf);
124            FileSystem fs = Services.get().get(HadoopAccessorService.class)
125                    .createFileSystem(launcherConf.get("user.name"),
126                                      launcherConf.get("group.name"), launcherConf);
127    
128            if (fs.exists(recoveryFile)) {
129                InputStream is = fs.open(recoveryFile);
130                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
131                jobId = reader.readLine();
132                reader.close();
133            }
134            return jobId;
135    
136        }
137    
138        public static void setupMainClass(Configuration launcherConf, String javaMainClass) {
139            launcherConf.set(CONF_OOZIE_ACTION_MAIN_CLASS, javaMainClass);
140        }
141    
142        public static void setupMainArguments(Configuration launcherConf, String[] args) {
143            launcherConf.setInt(CONF_OOZIE_ACTION_MAIN_ARG_COUNT, args.length);
144            for (int i = 0; i < args.length; i++) {
145                launcherConf.set(CONF_OOZIE_ACTION_MAIN_ARG_PREFIX + i, args[i]);
146            }
147        }
148    
149        public static void setupMaxOutputData(Configuration launcherConf, int maxOutputData) {
150            launcherConf.setInt(CONF_OOZIE_ACTION_MAX_OUTPUT_DATA, maxOutputData);
151        }
152    
153        /**
154         * @param launcherConf
155         * @param jobId
156         * @param actionId
157         * @param actionDir
158         * @param recoveryId
159         * @param actionConf
160         * @throws IOException
161         * @throws HadoopAccessorException
162         */
163        public static void setupLauncherInfo(JobConf launcherConf, String jobId, String actionId, Path actionDir,
164                String recoveryId, Configuration actionConf) throws IOException, HadoopAccessorException {
165    
166            launcherConf.setMapperClass(LauncherMapper.class);
167            launcherConf.setSpeculativeExecution(false);
168            launcherConf.setNumMapTasks(1);
169            launcherConf.setNumReduceTasks(0);
170    
171            launcherConf.set(OOZIE_JOB_ID, jobId);
172            launcherConf.set(OOZIE_ACTION_ID, actionId);
173            launcherConf.set(OOZIE_ACTION_DIR_PATH, actionDir.toString());
174            launcherConf.set(OOZIE_ACTION_RECOVERY_ID, recoveryId);
175    
176            actionConf.set(OOZIE_JOB_ID, jobId);
177            actionConf.set(OOZIE_ACTION_ID, actionId);
178    
179            FileSystem fs = Services.get().get(HadoopAccessorService.class).createFileSystem(launcherConf.get("user.name"),
180                    launcherConf.get("group.name"), launcherConf);
181            fs.mkdirs(actionDir);
182    
183            OutputStream os = fs.create(new Path(actionDir, ACTION_CONF_XML));
184            actionConf.writeXml(os);
185            os.close();
186    
187            Path inputDir = new Path(actionDir, "input");
188            fs.mkdirs(inputDir);
189            Writer writer = new OutputStreamWriter(fs.create(new Path(inputDir, "dummy.txt")));
190            writer.write("dummy");
191            writer.close();
192    
193            launcherConf.set("mapred.input.dir", inputDir.toString());
194            launcherConf.set("mapred.output.dir", new Path(actionDir, "output").toString());
195        }
196    
197        public static boolean isMainDone(RunningJob runningJob) throws IOException {
198            return runningJob.isComplete();
199        }
200    
201        public static boolean isMainSuccessful(RunningJob runningJob) throws IOException {
202            boolean succeeded = runningJob.isSuccessful();
203            if (succeeded) {
204                Counters counters = runningJob.getCounters();
205                if (counters != null) {
206                    Counters.Group group = counters.getGroup(COUNTER_GROUP);
207                    if (group != null) {
208                        succeeded = group.getCounter(COUNTER_LAUNCHER_ERROR) == 0;
209                    }
210                }
211            }
212            return succeeded;
213        }
214    
215        public static boolean hasOutputData(RunningJob runningJob) throws IOException {
216            boolean output = false;
217            Counters counters = runningJob.getCounters();
218            if (counters != null) {
219                Counters.Group group = counters.getGroup(COUNTER_GROUP);
220                if (group != null) {
221                    output = group.getCounter(COUNTER_OUTPUT_DATA) == 1;
222                }
223            }
224            return output;
225        }
226    
227        /**
228         * @param runningJob
229         * @return
230         * @throws IOException
231         */
232        public static boolean hasIdSwap(RunningJob runningJob) throws IOException {
233            boolean swap = false;
234            Counters counters = runningJob.getCounters();
235            if (counters != null) {
236                Counters.Group group = counters.getGroup(COUNTER_GROUP);
237                if (group != null) {
238                    swap = group.getCounter(COUNTER_DO_ID_SWAP) == 1;
239                }
240            }
241            return swap;
242        }
243    
244        /**
245         * @param runningJob
246         * @param user
247         * @param group
248         * @param actionDir
249         * @return
250         * @throws IOException
251         * @throws HadoopAccessorException
252         */
253        public static boolean hasIdSwap(RunningJob runningJob, String user, String group, Path actionDir)
254                throws IOException, HadoopAccessorException {
255            boolean swap = false;
256    
257            XLog log = XLog.getLog("org.apache.oozie.action.hadoop.LauncherMapper");
258    
259            Counters counters = runningJob.getCounters();
260            if (counters != null) {
261                Counters.Group counterGroup = counters.getGroup(COUNTER_GROUP);
262                if (counterGroup != null) {
263                    swap = counterGroup.getCounter(COUNTER_DO_ID_SWAP) == 1;
264                }
265            }
266            // additional check for swapped hadoop ID
267            // Can't rely on hadoop counters existing
268            // we'll check for the newID file in hdfs if the hadoop counters is null
269            else {
270    
271                Path p = getIdSwapPath(actionDir);
272                // log.debug("Checking for newId file in: [{0}]", p);
273    
274                FileSystem fs = Services.get().get(HadoopAccessorService.class).createFileSystem(user, group, p.toUri(),
275                                                                                                 new Configuration());
276                if (fs.exists(p)) {
277                    log.debug("Hadoop Counters is null, but found newID file.");
278    
279                    swap = true;
280                }
281                else {
282                    log.debug("Hadoop Counters is null, and newID file doesn't exist at: [{0}]", p);
283                }
284            }
285            return swap;
286        }
287    
288        public static Path getOutputDataPath(Path actionDir) {
289            return new Path(actionDir, ACTION_OUTPUT_PROPS);
290        }
291    
292        public static Path getErrorPath(Path actionDir) {
293            return new Path(actionDir, ACTION_ERROR_PROPS);
294        }
295    
296        public static Path getIdSwapPath(Path actionDir) {
297            return new Path(actionDir, ACTION_NEW_ID_PROPS);
298        }
299    
300        private JobConf jobConf;
301        private Path actionDir;
302        private ScheduledThreadPoolExecutor timer;
303    
304        private boolean configFailure = false;
305    
306        public LauncherMapper() {
307        }
308    
309        public void configure(JobConf jobConf) {
310            System.out.println();
311            System.out.println("Oozie Launcher starts");
312            System.out.println();
313            this.jobConf = jobConf;
314            actionDir = new Path(getJobConf().get(OOZIE_ACTION_DIR_PATH));
315            String recoveryId = jobConf.get(OOZIE_ACTION_RECOVERY_ID, null);
316            try {
317                setRecoveryId(jobConf, actionDir, recoveryId);
318            }
319            catch (LauncherException ex) {
320                configFailure = true;
321            }
322        }
323    
324        public void map(K1 key, V1 value, OutputCollector<K2, V2> collector, Reporter reporter) throws IOException {
325            try {
326                if (configFailure) {
327                    throw new LauncherException();
328                }
329                else {
330                    String mainClass = getJobConf().get(CONF_OOZIE_ACTION_MAIN_CLASS);
331                    String msgPrefix = "Main class [" + mainClass + "], ";
332                    int errorCode = 0;
333                    Throwable errorCause = null;
334                    String errorMessage = null;
335    
336                    try {
337                        new LauncherSecurityManager();
338                    }
339                    catch (SecurityException ex) {
340                        errorMessage = "Could not set LauncherSecurityManager";
341                        errorCause = ex;
342                    }
343    
344                    try {
345                        setupHeartBeater(reporter);
346    
347                        setupMainConfiguration();
348    
349                        String[] args = getMainArguments(getJobConf());
350    
351                        printContentsOfCurrentDir();
352    
353                        System.out.println();
354                        System.out.println("Oozie Java/Map-Reduce/Pig action launcher-job configuration");
355                        System.out.println("=================================================================");
356                        System.out.println("Workflow job id   : " + System.getProperty("oozie.job.id"));
357                        System.out.println("Workflow action id: " + System.getProperty("oozie.action.id"));
358                        System.out.println();
359                        System.out.println("Classpath         :");
360                        System.out.println("------------------------");
361                        StringTokenizer st = new StringTokenizer(System.getProperty("java.class.path"), ":");
362                        while (st.hasMoreTokens()) {
363                            System.out.println("  " + st.nextToken());
364                        }
365                        System.out.println("------------------------");
366                        System.out.println();
367                        System.out.println("Main class        : " + mainClass);
368                        System.out.println();
369                        System.out.println("Maximum output    : "
370                                + getJobConf().getInt(CONF_OOZIE_ACTION_MAX_OUTPUT_DATA, 2 * 1024));
371                        System.out.println();
372                        System.out.println("Arguments         :");
373                        for (String arg : args) {
374                            System.out.println("                    " + arg);
375                        }
376    
377                        System.out.println();
378                        System.out.println("Java System Properties:");
379                        System.out.println("------------------------");
380                        System.getProperties().store(System.out, "");
381                        System.out.flush();
382                        System.out.println("------------------------");
383                        System.out.println();
384    
385                        System.out.println("=================================================================");
386                        System.out.println();
387                        System.out.println(">>> Invoking Main class now >>>");
388                        System.out.println();
389                        System.out.flush();
390    
391                        try {
392                            Class klass = getJobConf().getClass(CONF_OOZIE_ACTION_MAIN_CLASS, Object.class);
393                            Method mainMethod = klass.getMethod("main", String[].class);
394                            mainMethod.invoke(null, (Object) args);
395                        }
396                        catch (InvocationTargetException ex) {
397                            if (LauncherMainException.class.isInstance(ex.getCause())) {
398                                errorMessage = msgPrefix + "exit code [" +((LauncherMainException)ex.getCause()).getErrorCode() 
399                                    + "]";
400                                errorCause = null;
401                            }
402                            else if (SecurityException.class.isInstance(ex.getCause())) {
403                                if (LauncherSecurityManager.getExitInvoked()) {
404                                    System.out.println("Intercepting System.exit(" + LauncherSecurityManager.getExitCode()
405                                            + ")");
406                                    System.err.println("Intercepting System.exit(" + LauncherSecurityManager.getExitCode()
407                                            + ")");
408                                    // if 0 main() method finished successfully
409                                    // ignoring
410                                    errorCode = LauncherSecurityManager.getExitCode();
411                                    if (errorCode != 0) {
412                                        errorMessage = msgPrefix + "exit code [" + errorCode + "]";
413                                        errorCause = null;
414                                    }
415                                }
416                            }
417                            else {
418                                throw ex;
419                            }
420                        }
421                        finally {
422                            System.out.println();
423                            System.out.println("<<< Invocation of Main class completed <<<");
424                            System.out.println();
425                        }
426                        if (errorMessage == null) {
427                            File outputData = new File(System.getProperty("oozie.action.output.properties"));
428                            if (outputData.exists()) {
429                                FileSystem fs = FileSystem.get(getJobConf());
430                                fs.copyFromLocalFile(new Path(outputData.toString()), new Path(actionDir,
431                                                                                               ACTION_OUTPUT_PROPS));
432                                reporter.incrCounter(COUNTER_GROUP, COUNTER_OUTPUT_DATA, 1);
433    
434                                int maxOutputData = getJobConf().getInt(CONF_OOZIE_ACTION_MAX_OUTPUT_DATA, 2 * 1024);
435                                if (outputData.length() > maxOutputData) {
436                                    String msg = MessageFormat.format("Output data size [{0}] exceeds maximum [{1}]",
437                                                                      outputData.length(), maxOutputData);
438                                    failLauncher(0, msg, null);
439                                }
440                                System.out.println();
441                                System.out.println("Oozie Launcher, capturing output data:");
442                                System.out.println("=======================");
443                                Properties props = new Properties();
444                                props.load(new FileReader(outputData));
445                                props.store(System.out, "");
446                                System.out.println();
447                                System.out.println("=======================");
448                                System.out.println();
449                            }
450                            File newId = new File(System.getProperty("oozie.action.newId.properties"));
451                            if (newId.exists()) {
452                                Properties props = new Properties();
453                                props.load(new FileReader(newId));
454                                if (props.getProperty("id") == null) {
455                                    throw new IllegalStateException("ID swap file does not have [id] property");
456                                }
457                                FileSystem fs = FileSystem.get(getJobConf());
458                                fs.copyFromLocalFile(new Path(newId.toString()), new Path(actionDir, ACTION_NEW_ID_PROPS));
459                                reporter.incrCounter(COUNTER_GROUP, COUNTER_DO_ID_SWAP, 1);
460    
461                                System.out.println("Oozie Launcher, copying new Hadoop job id to file: "
462                                        + new Path(actionDir, ACTION_NEW_ID_PROPS).toUri());
463    
464                                System.out.println();
465                                System.out.println("Oozie Launcher, propagating new Hadoop job id to Oozie");
466                                System.out.println("=======================");
467                                System.out.println("id: " + props.getProperty("id"));
468                                System.out.println("=======================");
469                                System.out.println();
470                            }
471                        }
472                    }
473                    catch (NoSuchMethodException ex) {
474                        errorMessage = msgPrefix + "main() method not found";
475                        errorCause = ex;
476                    }
477                    catch (InvocationTargetException ex) {
478                        errorMessage = msgPrefix + "main() threw exception";
479                        errorCause = ex.getTargetException();
480                    }
481                    catch (Throwable ex) {
482                        errorMessage = msgPrefix + "exception invoking main()";
483                        errorCause = ex;
484                    }
485                    finally {
486                        destroyHeartBeater();
487                        if (errorMessage != null) {
488                            failLauncher(errorCode, errorMessage, errorCause);
489                        }
490                    }
491                }
492            }
493            catch (LauncherException ex) {
494                reporter.incrCounter(COUNTER_GROUP, COUNTER_LAUNCHER_ERROR, 1);
495                System.out.println();
496                System.out.println("Oozie Launcher failed, finishing Hadoop job gracefully");
497                System.out.println();
498            }
499        }
500    
501        public void close() throws IOException {
502            System.out.println();
503            System.out.println("Oozie Launcher ends");
504            System.out.println();
505        }
506    
507        protected JobConf getJobConf() {
508            return jobConf;
509        }
510    
511        private void setupMainConfiguration() throws IOException {
512            FileSystem fs = FileSystem.get(getJobConf());
513            fs.copyToLocalFile(new Path(getJobConf().get(OOZIE_ACTION_DIR_PATH), ACTION_CONF_XML), new Path(new File(
514                    ACTION_CONF_XML).getAbsolutePath()));
515    
516            System.setProperty("oozie.launcher.job.id", getJobConf().get("mapred.job.id"));
517            System.setProperty("oozie.job.id", getJobConf().get(OOZIE_JOB_ID));
518            System.setProperty("oozie.action.id", getJobConf().get(OOZIE_ACTION_ID));
519            System.setProperty("oozie.action.conf.xml", new File(ACTION_CONF_XML).getAbsolutePath());
520            System.setProperty("oozie.action.output.properties", new File(ACTION_OUTPUT_PROPS).getAbsolutePath());
521            System.setProperty("oozie.action.newId.properties", new File(ACTION_NEW_ID_PROPS).getAbsolutePath());
522        }
523    
524        public static String[] getMainArguments(Configuration conf) {
525            String[] args = new String[conf.getInt(CONF_OOZIE_ACTION_MAIN_ARG_COUNT, 0)];
526            for (int i = 0; i < args.length; i++) {
527                args[i] = conf.get(CONF_OOZIE_ACTION_MAIN_ARG_PREFIX + i);
528            }
529            return args;
530        }
531    
532        private void setupHeartBeater(Reporter reporter) {
533            timer = new ScheduledThreadPoolExecutor(1);
534            timer.scheduleAtFixedRate(new LauncherMapper(reporter), 0, 30, TimeUnit.SECONDS);
535        }
536    
537        private void destroyHeartBeater() {
538            timer.shutdownNow();
539        }
540    
541        private Reporter reporter;
542    
543        private LauncherMapper(Reporter reporter) {
544            this.reporter = reporter;
545        }
546    
547        public void run() {
548            System.out.println("Heart beat");
549            reporter.progress();
550        }
551    
552        private void failLauncher(int errorCode, String reason, Throwable ex) throws LauncherException {
553            try {
554                if (ex != null) {
555                    reason += ", " + ex.getMessage();
556                }
557                Properties errorProps = new Properties();
558                errorProps.setProperty("error.code", Integer.toString(errorCode));
559                errorProps.setProperty("error.reason", reason);
560                if (ex != null) {
561                    if (ex.getMessage() != null) {
562                        errorProps.setProperty("exception.message", ex.getMessage());
563                    }
564                    StringWriter sw = new StringWriter();
565                    PrintWriter pw = new PrintWriter(sw);
566                    ex.printStackTrace(pw);
567                    pw.close();
568                    errorProps.setProperty("exception.stacktrace", sw.toString());
569                }
570                FileSystem fs = FileSystem.get(getJobConf());
571                OutputStream os = fs.create(new Path(actionDir, ACTION_ERROR_PROPS));
572                errorProps.store(os, "");
573                os.close();
574    
575                System.out.print("Failing Oozie Launcher, " + reason + "\n");
576                System.err.print("Failing Oozie Launcher, " + reason + "\n");
577                if (ex != null) {
578                    ex.printStackTrace(System.out);
579                    ex.printStackTrace(System.err);
580                }
581                throw new LauncherException();
582            }
583            catch (IOException rex) {
584                throw new RuntimeException("Error while failing launcher, " + rex.getMessage(), rex);
585            }
586        }
587    
588        /**
589         * Print files and directories in current directory. Will list files in the sub-directory (only 1 level deep)
590         */
591        protected void printContentsOfCurrentDir() {
592            File folder = new File(".");
593            System.out.println();
594            System.out.println("Files in current dir:" + folder.getAbsolutePath());
595            System.out.println("======================");
596    
597            File[] listOfFiles = folder.listFiles();
598            for (File fileName : listOfFiles) {
599                if (fileName.isFile()) {
600                    System.out.println("File: " + fileName.getName());
601                }
602                else if (fileName.isDirectory()) {
603                    System.out.println("Dir: " + fileName.getName());
604                    File subDir = new File(fileName.getName());
605                    File[] moreFiles = subDir.listFiles();
606                    for (File subFileName : moreFiles) {
607                        if (subFileName.isFile()) {
608                            System.out.println("  File: " + subFileName.getName());
609                        }
610                        else if (subFileName.isDirectory()) {
611                            System.out.println("  Dir: " + subFileName.getName());
612                        }
613                    }
614                }
615            }
616        }
617    
618    }
619    
620    class LauncherSecurityManager extends SecurityManager {
621        private static boolean exitInvoked;
622        private static int exitCode;
623        private SecurityManager securityManager;
624    
625        public LauncherSecurityManager() {
626            reset();
627            securityManager = System.getSecurityManager();
628            System.setSecurityManager(this);
629        }
630    
631        @Override
632        public void checkPermission(Permission perm, Object context) {
633            if (securityManager != null) {
634                // check everything with the original SecurityManager
635                securityManager.checkPermission(perm, context);
636            }
637        }
638    
639        @Override
640        public void checkPermission(Permission perm) {
641            if (securityManager != null) {
642                // check everything with the original SecurityManager
643                securityManager.checkPermission(perm);
644            }
645        }
646    
647        @Override
648        public void checkExit(int status) throws SecurityException {
649            exitInvoked = true;
650            exitCode = status;
651            throw new SecurityException("Intercepted System.exit(" + status + ")");
652        }
653    
654        public static boolean getExitInvoked() {
655            return exitInvoked;
656        }
657    
658        public static int getExitCode() {
659            return exitCode;
660        }
661    
662        public static void reset() {
663            exitInvoked = false;
664            exitCode = 0;
665        }
666    }
667    
668    class LauncherException extends Exception {
669    }