001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.main;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.CountDownLatch;
027import java.util.concurrent.TimeUnit;
028import java.util.concurrent.atomic.AtomicBoolean;
029
030import org.apache.camel.CamelContext;
031import org.apache.camel.ProducerTemplate;
032import org.apache.camel.builder.RouteBuilder;
033import org.apache.camel.impl.DefaultModelJAXBContextFactory;
034import org.apache.camel.model.ModelCamelContext;
035import org.apache.camel.model.RouteDefinition;
036import org.apache.camel.spi.ModelJAXBContextFactory;
037import org.apache.camel.support.ServiceSupport;
038import org.apache.camel.util.ServiceHelper;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * Base class for main implementations to allow starting up a JVM with Camel embedded.
044 *
045 * @version 
046 */
047public abstract class MainSupport extends ServiceSupport {
048    protected static final Logger LOG = LoggerFactory.getLogger(MainSupport.class);
049    protected final List<MainListener> listeners = new ArrayList<MainListener>();
050    protected final List<Option> options = new ArrayList<Option>();
051    protected final CountDownLatch latch = new CountDownLatch(1);
052    protected final AtomicBoolean completed = new AtomicBoolean(false);
053    protected long duration = -1;
054    protected TimeUnit timeUnit = TimeUnit.MILLISECONDS;
055    protected boolean trace;
056    protected List<RouteBuilder> routeBuilders = new ArrayList<RouteBuilder>();
057    protected String routeBuilderClasses;
058    protected final List<CamelContext> camelContexts = new ArrayList<CamelContext>();
059    protected ProducerTemplate camelTemplate;
060
061    /**
062     * A class for intercepting the hang up signal and do a graceful shutdown of the Camel.
063     */
064    private static final class HangupInterceptor extends Thread {
065        Logger log = LoggerFactory.getLogger(this.getClass());
066        MainSupport mainInstance;
067
068        public HangupInterceptor(MainSupport main) {
069            mainInstance = main;
070        }
071
072        @Override
073        public void run() {
074            log.info("Received hang up - stopping the main instance.");
075            try {
076                mainInstance.stop();
077            } catch (Exception ex) {
078                log.warn("Error during stopping the main instance.", ex);
079            }
080        }
081    }
082
083    protected MainSupport() {
084        addOption(new Option("h", "help", "Displays the help screen") {
085            protected void doProcess(String arg, LinkedList<String> remainingArgs) {
086                showOptions();
087                completed();
088            }
089        });
090        addOption(new ParameterOption("r", "routers",
091                 "Sets the router builder classes which will be loaded while starting the camel context",
092                 "routerBuilderClasses") {
093            @Override
094            protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
095                setRouteBuilderClasses(parameter);
096            }
097        });
098        addOption(new ParameterOption("d", "duration",
099                "Sets the time duration that the application will run for, by default in milliseconds. You can use '10s' for 10 seconds etc",
100                "duration") {
101            protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
102                String value = parameter.toUpperCase(Locale.ENGLISH);
103                if (value.endsWith("S")) {
104                    value = value.substring(0, value.length() - 1);
105                    setTimeUnit(TimeUnit.SECONDS);
106                }
107                setDuration(Integer.parseInt(value));
108            }
109        });
110        addOption(new Option("t", "trace", "Enables tracing") {
111            protected void doProcess(String arg, LinkedList<String> remainingArgs) {
112                enableTrace();
113            }
114        });
115    }
116
117    /**
118     * Runs this process with the given arguments, and will wait until completed, or the JVM terminates.
119     */
120    public void run() throws Exception {
121        if (!completed.get()) {
122            // if we have an issue starting then propagate the exception to caller
123            beforeStart();
124            start();
125            try {
126                afterStart();
127                waitUntilCompleted();
128                internalBeforeStop();
129                beforeStop();
130                stop();
131                afterStop();
132            } catch (Exception e) {
133                // however while running then just log errors
134                LOG.error("Failed: " + e, e);
135            }
136        }
137    }
138
139    /**
140     * Enables the hangup support. Gracefully stops by calling stop() on a
141     * Hangup signal.
142     */
143    public void enableHangupSupport() {
144        HangupInterceptor interceptor = new HangupInterceptor(this);
145        Runtime.getRuntime().addShutdownHook(interceptor);
146    }
147
148    /**
149     * Adds a {@link org.apache.camel.main.MainListener} to receive callbacks when the main is started or stopping
150     *
151     * @param listener the listener
152     */
153    public void addMainListener(MainListener listener) {
154        listeners.add(listener);
155    }
156
157    /**
158     * Removes the {@link org.apache.camel.main.MainListener}
159     *
160     * @param listener the listener
161     */
162    public void removeMainListener(MainListener listener) {
163        listeners.remove(listener);
164    }
165
166    /**
167     * Callback to run custom logic before CamelContext is being started.
168     * <p/>
169     * It is recommended to use {@link org.apache.camel.main.MainListener} instead.
170     */
171    protected void beforeStart() throws Exception {
172        for (MainListener listener : listeners) {
173            listener.beforeStart(this);
174        }
175    }
176
177    /**
178     * Callback to run custom logic after CamelContext has been started.
179     * <p/>
180     * It is recommended to use {@link org.apache.camel.main.MainListener} instead.
181     */
182    protected void afterStart() throws Exception {
183        for (MainListener listener : listeners) {
184            listener.afterStart(this);
185        }
186    }
187
188    /**
189     * Callback to run custom logic before CamelContext is being stopped.
190     * <p/>
191     * It is recommended to use {@link org.apache.camel.main.MainListener} instead.
192     */
193    protected void beforeStop() throws Exception {
194        for (MainListener listener : listeners) {
195            listener.beforeStop(this);
196        }
197    }
198
199    /**
200     * Callback to run custom logic after CamelContext has been stopped.
201     * <p/>
202     * It is recommended to use {@link org.apache.camel.main.MainListener} instead.
203     */
204    protected void afterStop() throws Exception {
205        for (MainListener listener : listeners) {
206            listener.afterStop(this);
207        }
208    }
209
210    private void internalBeforeStop() {
211        try {
212            if (camelTemplate != null) {
213                ServiceHelper.stopService(camelTemplate);
214                camelTemplate = null;
215            }
216        } catch (Exception e) {
217            LOG.debug("Error stopping camelTemplate due " + e.getMessage() + ". This exception is ignored.", e);
218        }
219    }
220
221    /**
222     * Marks this process as being completed.
223     */
224    public void completed() {
225        completed.set(true);
226        latch.countDown();
227    }
228
229    /**
230     * Displays the command line options.
231     */
232    public void showOptions() {
233        showOptionsHeader();
234
235        for (Option option : options) {
236            System.out.println(option.getInformation());
237        }
238    }
239
240    /**
241     * Parses the command line arguments.
242     */
243    public void parseArguments(String[] arguments) {
244        LinkedList<String> args = new LinkedList<String>(Arrays.asList(arguments));
245
246        boolean valid = true;
247        while (!args.isEmpty()) {
248            String arg = args.removeFirst();
249
250            boolean handled = false;
251            for (Option option : options) {
252                if (option.processOption(arg, args)) {
253                    handled = true;
254                    break;
255                }
256            }
257            if (!handled) {
258                System.out.println("Unknown option: " + arg);
259                System.out.println();
260                valid = false;
261                break;
262            }
263        }
264        if (!valid) {
265            showOptions();
266            completed();
267        }
268    }
269
270    public void addOption(Option option) {
271        options.add(option);
272    }
273
274    public long getDuration() {
275        return duration;
276    }
277
278    /**
279     * Sets the duration to run the application for in milliseconds until it
280     * should be terminated. Defaults to -1. Any value <= 0 will run forever.
281     */
282    public void setDuration(long duration) {
283        this.duration = duration;
284    }
285
286    public TimeUnit getTimeUnit() {
287        return timeUnit;
288    }
289
290    /**
291     * Sets the time unit duration.
292     */
293    public void setTimeUnit(TimeUnit timeUnit) {
294        this.timeUnit = timeUnit;
295    }
296
297    public void setRouteBuilderClasses(String builders) {
298        this.routeBuilderClasses = builders;
299    }
300
301    public String getRouteBuilderClasses() {
302        return routeBuilderClasses;
303    }
304
305    public boolean isTrace() {
306        return trace;
307    }
308
309    public void enableTrace() {
310        this.trace = true;
311    }
312
313    protected void doStop() throws Exception {
314        // call completed to properly stop as we count down the waiting latch
315        completed();
316    }
317
318    protected void doStart() throws Exception {
319    }
320
321    protected void waitUntilCompleted() {
322        while (!completed.get()) {
323            try {
324                if (duration > 0) {
325                    TimeUnit unit = getTimeUnit();
326                    LOG.info("Waiting for: " + duration + " " + unit);
327                    latch.await(duration, unit);
328                    completed.set(true);
329                } else {
330                    latch.await();
331                }
332            } catch (InterruptedException e) {
333                Thread.currentThread().interrupt();
334            }
335        }
336    }
337
338    /**
339     * Parses the command line arguments then runs the program.
340     */
341    public void run(String[] args) throws Exception {
342        parseArguments(args);
343        run();
344    }
345
346    /**
347     * Displays the header message for the command line options.
348     */
349    public void showOptionsHeader() {
350        System.out.println("Apache Camel Runner takes the following options");
351        System.out.println();
352    }
353
354    public List<CamelContext> getCamelContexts() {
355        return camelContexts;
356    }
357
358    public List<RouteBuilder> getRouteBuilders() {
359        return routeBuilders;
360    }
361
362    public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
363        this.routeBuilders = routeBuilders;
364    }
365
366    public List<RouteDefinition> getRouteDefinitions() {
367        List<RouteDefinition> answer = new ArrayList<RouteDefinition>();
368        for (CamelContext camelContext : camelContexts) {
369            answer.addAll(((ModelCamelContext)camelContext).getRouteDefinitions());
370        }
371        return answer;
372    }
373
374    public ProducerTemplate getCamelTemplate() throws Exception {
375        if (camelTemplate == null) {
376            camelTemplate = findOrCreateCamelTemplate();
377        }
378        return camelTemplate;
379    }
380
381    protected abstract ProducerTemplate findOrCreateCamelTemplate();
382
383    protected abstract Map<String, CamelContext> getCamelContextMap();
384
385    protected void postProcessContext() throws Exception {
386        Map<String, CamelContext> map = getCamelContextMap();
387        Set<Map.Entry<String, CamelContext>> entries = map.entrySet();
388        for (Map.Entry<String, CamelContext> entry : entries) {
389            CamelContext camelContext = entry.getValue();
390            camelContexts.add(camelContext);
391            postProcessCamelContext(camelContext);
392        }
393    }
394
395    public ModelJAXBContextFactory getModelJAXBContextFactory() {
396        return new DefaultModelJAXBContextFactory();
397    }
398
399    protected void loadRouteBuilders(CamelContext camelContext) throws Exception {
400        if (routeBuilderClasses != null) {
401            // get the list of route builder classes
402            String[] routeClasses = routeBuilderClasses.split(",");
403            for (String routeClass : routeClasses) {
404                Class<?> routeClazz = camelContext.getClassResolver().resolveClass(routeClass);
405                RouteBuilder builder = (RouteBuilder) routeClazz.newInstance();
406                getRouteBuilders().add(builder);
407            }
408        }
409    }
410
411    protected void postProcessCamelContext(CamelContext camelContext) throws Exception {
412        if (trace) {
413            camelContext.setTracing(true);
414        }
415        // try to load the route builders from the routeBuilderClasses
416        loadRouteBuilders(camelContext);
417        for (RouteBuilder routeBuilder : routeBuilders) {
418            camelContext.addRoutes(routeBuilder);
419        }
420        // register lifecycle so we are notified in Camel is stopped from JMX or somewhere else
421        camelContext.addLifecycleStrategy(new MainLifecycleStrategy(completed, latch));
422        // allow to do configuration before its started
423        for (MainListener listener : listeners) {
424            listener.configure(camelContext);
425        }
426    }
427
428    public void addRouteBuilder(RouteBuilder routeBuilder) {
429        getRouteBuilders().add(routeBuilder);
430    }
431
432    public abstract class Option {
433        private String abbreviation;
434        private String fullName;
435        private String description;
436
437        protected Option(String abbreviation, String fullName, String description) {
438            this.abbreviation = "-" + abbreviation;
439            this.fullName = "-" + fullName;
440            this.description = description;
441        }
442
443        public boolean processOption(String arg, LinkedList<String> remainingArgs) {
444            if (arg.equalsIgnoreCase(abbreviation) || fullName.startsWith(arg)) {
445                doProcess(arg, remainingArgs);
446                return true;
447            }
448            return false;
449        }
450
451        public String getAbbreviation() {
452            return abbreviation;
453        }
454
455        public String getDescription() {
456            return description;
457        }
458
459        public String getFullName() {
460            return fullName;
461        }
462
463        public String getInformation() {
464            return "  " + getAbbreviation() + " or " + getFullName() + " = " + getDescription();
465        }
466
467        protected abstract void doProcess(String arg, LinkedList<String> remainingArgs);
468    }
469
470    public abstract class ParameterOption extends Option {
471        private String parameterName;
472
473        protected ParameterOption(String abbreviation, String fullName, String description, String parameterName) {
474            super(abbreviation, fullName, description);
475            this.parameterName = parameterName;
476        }
477
478        protected void doProcess(String arg, LinkedList<String> remainingArgs) {
479            if (remainingArgs.isEmpty()) {
480                System.err.println("Expected fileName for ");
481                showOptions();
482                completed();
483            } else {
484                String parameter = remainingArgs.removeFirst();
485                doProcess(arg, parameter, remainingArgs);
486            }
487        }
488
489        public String getInformation() {
490            return "  " + getAbbreviation() + " or " + getFullName() + " <" + parameterName + "> = " + getDescription();
491        }
492
493        protected abstract void doProcess(String arg, String parameter, LinkedList<String> remainingArgs);
494    }
495}