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