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.processor;
018
019import java.util.HashSet;
020import java.util.Map;
021import java.util.Set;
022
023import org.apache.camel.Exchange;
024import org.apache.camel.processor.aggregate.AggregationStrategy;
025import org.apache.camel.util.EndpointHelper;
026import org.apache.camel.util.ObjectHelper;
027import org.apache.camel.util.StringHelper;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Default {@link AggregationStrategy} used by the {@link ClaimCheckProcessor} EIP.
033 * <p/>
034 * This strategy supports the following include rules syntax:
035 * <ul>
036 *     <li>body</li> - to aggregate the message body
037 *     <li>attachments</li> - to aggregate all the message attachments
038 *     <li>headers</li> - to aggregate all the message headers
039 *     <li>header:pattern</li> - to aggregate all the message headers that matches the pattern.
040 *     The pattern syntax is documented by: {@link EndpointHelper#matchPattern(String, String)}.
041 * </ul>
042 * You can specify multiple rules separated by comma. For example to include the message body and all headers starting with foo
043 * <tt>body,header:foo*</tt>.
044 * If the include rule is specified as empty or as wildcard then everything is merged.
045 * If you have configured both include and exclude then exclude take precedence over include.
046 */
047public class ClaimCheckAggregationStrategy implements AggregationStrategy {
048
049    private static final Logger LOG = LoggerFactory.getLogger(ClaimCheckAggregationStrategy.class);
050    private String filter;
051
052    public ClaimCheckAggregationStrategy() {
053    }
054
055    public String getFilter() {
056        return filter;
057    }
058
059    public void setFilter(String filter) {
060        this.filter = filter;
061    }
062
063    @Override
064    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
065        if (newExchange == null) {
066            return oldExchange;
067        }
068
069        if (ObjectHelper.isEmpty(filter) || "*".equals(filter)) {
070            // grab everything
071            oldExchange.getMessage().setBody(newExchange.getMessage().getBody());
072            LOG.trace("Including: body");
073            if (newExchange.getMessage().hasHeaders()) {
074                oldExchange.getMessage().getHeaders().putAll(newExchange.getMessage().getHeaders());
075                LOG.trace("Including: headers");
076            }
077            if (newExchange.getMessage().hasAttachments()) {
078                oldExchange.getMessage().getAttachments().putAll(newExchange.getMessage().getAttachments());
079                LOG.trace("Including: attachments");
080            }
081            return oldExchange;
082        }
083
084        // body is by default often included
085        if (isBodyEnabled()) {
086            oldExchange.getMessage().setBody(newExchange.getMessage().getBody());
087            LOG.trace("Including: body");
088        }
089
090        // attachments is by default often included
091        if (isAttachmentsEnabled()) {
092            if (newExchange.getMessage().hasAttachments()) {
093                oldExchange.getMessage().getAttachments().putAll(newExchange.getMessage().getAttachments());
094                LOG.trace("Including: attachments");
095            }
096        }
097
098        // headers is by default often included
099        if (isHeadersEnabled()) {
100            if (newExchange.getMessage().hasHeaders()) {
101                oldExchange.getMessage().getHeaders().putAll(newExchange.getMessage().getHeaders());
102                LOG.trace("Including: headers");
103            }
104        }
105
106        // filter specific header if they are somehow enabled by the filter
107        if (hasHeaderPatterns()) {
108            boolean excludeOnly = isExcludeOnlyHeaderPatterns();
109            for (Map.Entry<String, Object> header : newExchange.getMessage().getHeaders().entrySet()) {
110                String key = header.getKey();
111                if (hasHeaderPattern(key)) {
112                    boolean include = isIncludedHeader(key);
113                    boolean exclude = isExcludedHeader(key);
114                    if (include) {
115                        LOG.trace("Including: header:{}", key);
116                        oldExchange.getMessage().getHeaders().put(key, header.getValue());
117                    } else if (exclude) {
118                        LOG.trace("Excluding: header:{}", key);
119                    } else {
120                        LOG.trace("Skipping: header:{}", key);
121                    }
122                } else if (excludeOnly) {
123                    LOG.trace("Including: header:{}", key);
124                    oldExchange.getMessage().getHeaders().put(key, header.getValue());
125                }
126            }
127        }
128
129        // filter body and all headers
130        if (ObjectHelper.isNotEmpty(filter)) {
131            Iterable it = ObjectHelper.createIterable(filter, ",");
132            for (Object k : it) {
133                String part = k.toString();
134                if (("body".equals(part) || "+body".equals(part)) && !"-body".equals(part)) {
135                    oldExchange.getMessage().setBody(newExchange.getMessage().getBody());
136                    LOG.trace("Including: body");
137                } else if (("headers".equals(part) || "+headers".equals(part)) && !"-headers".equals(part)) {
138                    oldExchange.getMessage().getHeaders().putAll(newExchange.getMessage().getHeaders());
139                    LOG.trace("Including: headers");
140                }
141            }
142        }
143
144        // filter with remove (--) take precedence at the end
145        Iterable it = ObjectHelper.createIterable(filter, ",");
146        for (Object k : it) {
147            String part = k.toString();
148            if ("--body".equals(part)) {
149                oldExchange.getMessage().setBody(null);
150            } else if ("--headers".equals(part)) {
151                oldExchange.getMessage().getHeaders().clear();
152            } else if (part.startsWith("--header:")) {
153                // pattern matching for headers, eg header:foo, header:foo*, header:(foo|bar)
154                String after = StringHelper.after(part, "--header:");
155                Iterable i = ObjectHelper.createIterable(after, ",");
156                Set<String> toRemoveKeys = new HashSet<>();
157                for (Object o : i) {
158                    String pattern = o.toString();
159                    for (Map.Entry<String, Object> header : oldExchange.getMessage().getHeaders().entrySet()) {
160                        String key = header.getKey();
161                        boolean matched = EndpointHelper.matchPattern(key, pattern);
162                        if (matched) {
163                            toRemoveKeys.add(key);
164                        }
165                    }
166                }
167                for (String key : toRemoveKeys) {
168                    LOG.trace("Removing: header:{}", key);
169                    oldExchange.getMessage().removeHeader(key);
170                }
171            }
172        }
173
174        return oldExchange;
175    }
176
177    private boolean hasHeaderPatterns() {
178        String[] parts = filter.split(",");
179        for (String pattern : parts) {
180            if (pattern.startsWith("--")) {
181                continue;
182            }
183            if (pattern.startsWith("header:") || pattern.startsWith("+header:") || pattern.startsWith("-header:")) {
184                return true;
185            }
186        }
187        return false;
188    }
189
190    private boolean isExcludeOnlyHeaderPatterns() {
191        String[] parts = filter.split(",");
192        for (String pattern : parts) {
193            if (pattern.startsWith("--")) {
194                continue;
195            }
196            if (pattern.startsWith("header:") || pattern.startsWith("+header:")) {
197                return false;
198            }
199        }
200        return true;
201    }
202
203    private boolean hasHeaderPattern(String key) {
204        String[] parts = filter.split(",");
205        for (String pattern : parts) {
206            if (pattern.startsWith("--")) {
207                continue;
208            }
209            String header = null;
210            if (pattern.startsWith("header:") || pattern.startsWith("+header:")) {
211                header = StringHelper.after(pattern, "header:");
212            } else if (pattern.startsWith("-header:")) {
213                header = StringHelper.after(pattern, "-header:");
214            }
215            if (header != null && EndpointHelper.matchPattern(key, header)) {
216                return true;
217            }
218        }
219        return false;
220    }
221
222    private boolean isIncludedHeader(String key) {
223        String[] parts = filter.split(",");
224        for (String pattern : parts) {
225            if (pattern.startsWith("--")) {
226                continue;
227            }
228            if (pattern.startsWith("header:") || pattern.startsWith("+header:")) {
229                pattern = StringHelper.after(pattern, "header:");
230            }
231            if (EndpointHelper.matchPattern(key, pattern)) {
232                return true;
233            }
234        }
235        return false;
236    }
237
238    private boolean isExcludedHeader(String key) {
239        String[] parts = filter.split(",");
240        for (String pattern : parts) {
241            if (pattern.startsWith("--")) {
242                continue;
243            }
244            if (pattern.startsWith("-header:")) {
245                pattern = StringHelper.after(pattern, "-header:");
246            }
247            if (EndpointHelper.matchPattern(key, pattern)) {
248                return true;
249            }
250        }
251        return false;
252    }
253
254    private boolean isBodyEnabled() {
255        // body is always enabled unless excluded
256        String[] parts = filter.split(",");
257
258        boolean onlyExclude = true;
259        for (String pattern : parts) {
260            if (pattern.startsWith("--")) {
261                continue;
262            }
263            if ("body".equals(pattern) || "+body".equals(pattern)) {
264                return true;
265            } else if ("-body".equals(pattern)) {
266                return false;
267            }
268            onlyExclude &= pattern.startsWith("-");
269        }
270        // body is enabled if we only have exclude patterns
271        return onlyExclude;
272    }
273
274    private boolean isAttachmentsEnabled() {
275        // attachments is always enabled unless excluded
276        String[] parts = filter.split(",");
277
278        boolean onlyExclude = true;
279        for (String pattern : parts) {
280            if (pattern.startsWith("--")) {
281                continue;
282            }
283            if ("attachments".equals(pattern) || "+attachments".equals(pattern)) {
284                return true;
285            } else if ("-attachments".equals(pattern)) {
286                return false;
287            }
288            onlyExclude &= pattern.startsWith("-");
289        }
290        // attachments is enabled if we only have exclude patterns
291        return onlyExclude;
292    }
293
294    private boolean isHeadersEnabled() {
295        // headers may be enabled unless excluded
296        String[] parts = filter.split(",");
297
298        boolean onlyExclude = true;
299        for (String pattern : parts) {
300            if (pattern.startsWith("--")) {
301                continue;
302            }
303            // if there is individual header filters then we cannot rely on this
304            if (pattern.startsWith("header:") || pattern.startsWith("+header:") || pattern.startsWith("-header:")) {
305                return false;
306            }
307            if ("headers".equals(pattern) || "+headers".equals(pattern)) {
308                return true;
309            } else if ("-headers".equals(pattern)) {
310                return false;
311            }
312            onlyExclude &= pattern.startsWith("-");
313        }
314        // headers is enabled if we only have exclude patterns
315        return onlyExclude;
316    }
317
318}