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.impl;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.Date;
023import java.util.LinkedList;
024import java.util.List;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027import java.util.concurrent.atomic.AtomicInteger;
028import java.util.stream.Collectors;
029import java.util.stream.Stream;
030
031import org.apache.camel.Endpoint;
032import org.apache.camel.Exchange;
033import org.apache.camel.MessageHistory;
034import org.apache.camel.spi.InflightRepository;
035import org.apache.camel.support.ServiceSupport;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * Default {@link org.apache.camel.spi.InflightRepository}.
041 *
042 * @version 
043 */
044public class DefaultInflightRepository extends ServiceSupport implements InflightRepository {
045
046    private static final Logger LOG = LoggerFactory.getLogger(DefaultInflightRepository.class);
047    private final ConcurrentMap<String, Exchange> inflight = new ConcurrentHashMap<String, Exchange>();
048    private final ConcurrentMap<String, AtomicInteger> routeCount = new ConcurrentHashMap<String, AtomicInteger>();
049
050    public void add(Exchange exchange) {
051        inflight.put(exchange.getExchangeId(), exchange);
052    }
053
054    public void remove(Exchange exchange) {
055        inflight.remove(exchange.getExchangeId());
056    }
057
058    public void add(Exchange exchange, String routeId) {
059        AtomicInteger existing = routeCount.get(routeId);
060        if (existing != null) {
061            existing.incrementAndGet();
062        }
063    }
064
065    public void remove(Exchange exchange, String routeId) {
066        AtomicInteger existing = routeCount.get(routeId);
067        if (existing != null) {
068            existing.decrementAndGet();
069        }
070    }
071
072    public int size() {
073        return inflight.size();
074    }
075
076    @Deprecated
077    public int size(Endpoint endpoint) {
078        return 0;
079    }
080
081    @Override
082    public void addRoute(String routeId) {
083        routeCount.putIfAbsent(routeId, new AtomicInteger(0));
084    }
085
086    @Override
087    public void removeRoute(String routeId) {
088        routeCount.remove(routeId);
089    }
090
091    @Override
092    public int size(String routeId) {
093        AtomicInteger existing = routeCount.get(routeId);
094        return existing != null ? existing.get() : 0;
095    }
096
097    @Override
098    public Collection<InflightExchange> browse() {
099        return browse(null, -1, false);
100    }
101
102    @Override
103    public Collection<InflightExchange> browse(String fromRouteId) {
104        return browse(fromRouteId, -1, false);
105    }
106
107    @Override
108    public Collection<InflightExchange> browse(int limit, boolean sortByLongestDuration) {
109        return browse(null, limit, sortByLongestDuration);
110    }
111
112    @Override
113    public Collection<InflightExchange> browse(String fromRouteId, int limit, boolean sortByLongestDuration) {
114        Stream<Exchange> values;
115        if (fromRouteId == null) {
116            // all values
117            values = inflight.values().stream();
118        } else {
119            // only if route match
120            values = inflight.values().stream()
121                .filter(e -> fromRouteId.equals(e.getFromRouteId()));
122        }
123
124        if (sortByLongestDuration) {
125            // sort by duration and grab the first
126            values = values.sorted((e1, e2) -> {
127                long d1 = getExchangeDuration(e1);
128                long d2 = getExchangeDuration(e2);
129                // need the biggest number first
130                return -1 * Long.compare(d1, d2);
131            });
132        } else {
133            // else sort by exchange id
134            values = values.sorted(Comparator.comparing(Exchange::getExchangeId));
135        }
136
137        if (limit > 0) {
138            values = values.limit(limit);
139        }
140
141        List<InflightExchange> answer = values.map(InflightExchangeEntry::new).collect(Collectors.toList());
142        return Collections.unmodifiableCollection(answer);
143    }
144
145    @Override
146    public InflightExchange oldest(String fromRouteId) {
147        Stream<Exchange> values;
148
149        if (fromRouteId == null) {
150            // all values
151            values = inflight.values().stream();
152        } else {
153            // only if route match
154            values = inflight.values().stream()
155                .filter(e -> fromRouteId.equals(e.getFromRouteId()));
156        }
157
158        // sort by duration and grab the first
159        Exchange first = values.sorted((e1, e2) -> {
160            long d1 = getExchangeDuration(e1);
161            long d2 = getExchangeDuration(e2);
162            // need the biggest number first
163            return -1 * Long.compare(d1, d2);
164        }).findFirst().orElse(null);
165
166        if (first != null) {
167            return new InflightExchangeEntry(first);
168        } else {
169            return null;
170        }
171    }
172
173    @Override
174    protected void doStart() throws Exception {
175    }
176
177    @Override
178    protected void doStop() throws Exception {
179        int count = size();
180        if (count > 0) {
181            LOG.warn("Shutting down while there are still " + count + " inflight exchanges.");
182        } else {
183            LOG.debug("Shutting down with no inflight exchanges.");
184        }
185        routeCount.clear();
186    }
187
188    private static long getExchangeDuration(Exchange exchange) {
189        long duration = 0;
190        Date created = exchange.getCreated();
191        if (created != null) {
192            duration = System.currentTimeMillis() - created.getTime();
193        }
194        return duration;
195    }
196
197    private static final class InflightExchangeEntry implements InflightExchange {
198
199        private final Exchange exchange;
200
201        private InflightExchangeEntry(Exchange exchange) {
202            this.exchange = exchange;
203        }
204
205        @Override
206        public Exchange getExchange() {
207            return exchange;
208        }
209
210        @Override
211        public long getDuration() {
212            return DefaultInflightRepository.getExchangeDuration(exchange);
213        }
214
215        @Override
216        @SuppressWarnings("unchecked")
217        public long getElapsed() {
218            LinkedList<MessageHistory> list = exchange.getProperty(Exchange.MESSAGE_HISTORY, LinkedList.class);
219            if (list == null || list.isEmpty()) {
220                return 0;
221            }
222
223            // get latest entry
224            MessageHistory history = list.getLast();
225            if (history != null) {
226                return history.getElapsed();
227            } else {
228                return 0;
229            }
230        }
231
232        @Override
233        @SuppressWarnings("unchecked")
234        public String getNodeId() {
235            LinkedList<MessageHistory> list = exchange.getProperty(Exchange.MESSAGE_HISTORY, LinkedList.class);
236            if (list == null || list.isEmpty()) {
237                return null;
238            }
239
240            // get latest entry
241            MessageHistory history = list.getLast();
242            if (history != null) {
243                return history.getNode().getId();
244            } else {
245                return null;
246            }
247        }
248
249        @Override
250        public String getFromRouteId() {
251            return exchange.getFromRouteId();
252        }
253
254        @Override
255        public String getRouteId() {
256            return getAtRouteId();
257        }
258
259        @Override
260        @SuppressWarnings("unchecked")
261        public String getAtRouteId() {
262            LinkedList<MessageHistory> list = exchange.getProperty(Exchange.MESSAGE_HISTORY, LinkedList.class);
263            if (list == null || list.isEmpty()) {
264                return null;
265            }
266
267            // get latest entry
268            MessageHistory history = list.getLast();
269            if (history != null) {
270                return history.getRouteId();
271            } else {
272                return null;
273            }
274        }
275
276        @Override
277        public String toString() {
278            return "InflightExchangeEntry[exchangeId=" + exchange.getExchangeId() + "]";
279        }
280    }
281
282}