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.EventObject;
020import java.util.concurrent.CountDownLatch;
021import java.util.concurrent.ScheduledExecutorService;
022import java.util.concurrent.TimeUnit;
023import java.util.concurrent.atomic.AtomicBoolean;
024
025import org.apache.camel.CamelContext;
026import org.apache.camel.management.event.ExchangeCompletedEvent;
027import org.apache.camel.management.event.ExchangeCreatedEvent;
028import org.apache.camel.management.event.ExchangeFailedEvent;
029import org.apache.camel.support.EventNotifierSupport;
030import org.apache.camel.util.StopWatch;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * A {@link org.apache.camel.spi.EventNotifier} to trigger shutdown of the Main JVM
036 * when maximum number of messages has been processed.
037 */
038public class MainDurationEventNotifier extends EventNotifierSupport {
039
040    private static final Logger LOG = LoggerFactory.getLogger(MainLifecycleStrategy.class);
041    private final CamelContext camelContext;
042    private final int maxMessages;
043    private final long maxIdleSeconds;
044    private final AtomicBoolean completed;
045    private final CountDownLatch latch;
046    private final boolean stopCamelContext;
047
048    private volatile int doneMessages;
049    private volatile StopWatch watch;
050    private volatile ScheduledExecutorService executorService;
051
052    public MainDurationEventNotifier(CamelContext camelContext, int maxMessages, long maxIdleSeconds,
053                                     AtomicBoolean completed, CountDownLatch latch, boolean stopCamelContext) {
054        this.camelContext = camelContext;
055        this.maxMessages = maxMessages;
056        this.maxIdleSeconds = maxIdleSeconds;
057        this.completed = completed;
058        this.latch = latch;
059        this.stopCamelContext = stopCamelContext;
060    }
061
062    @Override
063    public void notify(EventObject event) throws Exception {
064        boolean begin = event instanceof ExchangeCreatedEvent;
065        boolean complete = event instanceof ExchangeCompletedEvent || event instanceof ExchangeFailedEvent;
066
067        if (maxMessages > 0 && complete) {
068            doneMessages++;
069
070            boolean result = doneMessages >= maxMessages;
071            LOG.trace("Duration max messages check {} >= {} -> {}", doneMessages, maxMessages, result);
072
073            if (result) {
074                if (completed.compareAndSet(false, true)) {
075                    LOG.info("Duration max messages triggering shutdown of the JVM.");
076                    try {
077                        // shutting down CamelContext
078                        if (stopCamelContext) {
079                            camelContext.stop();
080                        }
081                    } catch (Exception e) {
082                        LOG.warn("Error during stopping CamelContext. This exception is ignored.", e);
083                    } finally {
084                        // trigger stopping the Main
085                        latch.countDown();
086                    }
087                }
088            }
089        }
090
091        // idle reacts on both incoming and complete messages
092        if (maxIdleSeconds > 0 && (begin || complete)) {
093            LOG.trace("Message activity so restarting stop watch");
094            watch.restart();
095        }
096    }
097
098    @Override
099    public boolean isEnabled(EventObject event) {
100        return event instanceof ExchangeCompletedEvent || event instanceof ExchangeFailedEvent;
101    }
102
103    @Override
104    public String toString() {
105        return "MainDurationEventNotifier[" + maxMessages + " max messages]";
106    }
107
108    @Override
109    protected void doStart() throws Exception {
110        if (maxIdleSeconds > 0) {
111
112            // we only start watch when Camel is started
113            camelContext.addStartupListener((context, alreadyStarted) -> watch = new StopWatch());
114
115            // okay we need to trigger on idle after X period, and therefore we need a background task that checks this
116            executorService = camelContext.getExecutorServiceManager().newSingleThreadScheduledExecutor(this, "MainDurationIdleChecker");
117            Runnable task = () -> {
118                if (watch == null) {
119                    // camel has not been started yet
120                    return;
121                }
122
123                // any inflight messages currently
124                int inflight = camelContext.getInflightRepository().size();
125                if (inflight > 0) {
126                    LOG.trace("Duration max idle check is skipped due {} inflight messages", inflight);
127                    return;
128                }
129
130                long seconds = watch.taken() / 1000;
131                boolean result = seconds >= maxIdleSeconds;
132                LOG.trace("Duration max idle check {} >= {} -> {}", seconds, maxIdleSeconds, result);
133
134                if (result) {
135                    if (completed.compareAndSet(false, true)) {
136                        LOG.info("Duration max idle triggering shutdown of the JVM.");
137                        try {
138                            // shutting down CamelContext
139                            if (stopCamelContext) {
140                                camelContext.stop();
141                            }
142                        } catch (Exception e) {
143                            LOG.warn("Error during stopping CamelContext. This exception is ignored.", e);
144                        } finally {
145                            // trigger stopping the Main
146                            latch.countDown();
147                        }
148                    }
149                }
150            };
151            executorService.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);
152        }
153    }
154
155}