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.model;
018
019import javax.xml.bind.annotation.XmlAccessType;
020import javax.xml.bind.annotation.XmlAccessorType;
021import javax.xml.bind.annotation.XmlAttribute;
022import javax.xml.bind.annotation.XmlRootElement;
023import javax.xml.bind.annotation.XmlTransient;
024
025import org.apache.camel.CamelContextAware;
026import org.apache.camel.Processor;
027import org.apache.camel.processor.ClaimCheckProcessor;
028import org.apache.camel.processor.aggregate.AggregationStrategy;
029import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter;
030import org.apache.camel.spi.Metadata;
031import org.apache.camel.spi.RouteContext;
032import org.apache.camel.util.EndpointHelper;
033import org.apache.camel.util.ObjectHelper;
034
035/**
036 * The Claim Check EIP allows you to replace message content with a claim check (a unique key),
037 * which can be used to retrieve the message content at a later time.
038 */
039@Metadata(label = "eip,routing")
040@XmlRootElement(name = "claimCheck")
041@XmlAccessorType(XmlAccessType.FIELD)
042public class ClaimCheckDefinition extends NoOutputDefinition<ClaimCheckDefinition> {
043
044    @XmlAttribute(required = true)
045    private ClaimCheckOperation operation;
046    @XmlAttribute
047    private String key;
048    @XmlAttribute
049    private String filter;
050    @XmlAttribute(name = "strategyRef") @Metadata(label = "advanced")
051    private String aggregationStrategyRef;
052    @XmlAttribute(name = "strategyMethodName") @Metadata(label = "advanced")
053    private String aggregationStrategyMethodName;
054    @XmlTransient
055    private AggregationStrategy aggregationStrategy;
056
057    public ClaimCheckDefinition() {
058    }
059
060    @Override
061    public String toString() {
062        if (operation != null) {
063            return "ClaimCheck[" + operation + "]";
064        } else {
065            return "ClaimCheck";
066        }
067    }
068
069    @Override
070    public String getLabel() {
071        return "claimCheck";
072    }
073
074    @Override
075    public Processor createProcessor(RouteContext routeContext) throws Exception {
076        ObjectHelper.notNull(operation, "operation", this);
077
078        ClaimCheckProcessor claim = new ClaimCheckProcessor();
079        claim.setOperation(operation.name());
080        claim.setKey(getKey());
081        claim.setFilter(getFilter());
082
083        AggregationStrategy strategy = createAggregationStrategy(routeContext);
084        if (strategy != null) {
085            claim.setAggregationStrategy(strategy);
086        }
087
088        // only filter or aggregation strategy can be configured not both
089        if (getFilter() != null && strategy != null) {
090            throw new IllegalArgumentException("Cannot use both filter and custom aggregation strategy on ClaimCheck EIP");
091        }
092
093        // validate filter, we cannot have both +/- at the same time
094        if (getFilter() != null) {
095            Iterable it = ObjectHelper.createIterable(filter, ",");
096            boolean includeBody = false;
097            boolean excludeBody = false;
098            for (Object o : it) {
099                String pattern = o.toString();
100                if ("body".equals(pattern) || "+body".equals(pattern)) {
101                    includeBody = true;
102                } else if ("-body".equals(pattern)) {
103                    excludeBody = true;
104                }
105            }
106            if (includeBody && excludeBody) {
107                throw new IllegalArgumentException("Cannot have both include and exclude body at the same time in the filter: " + filter);
108            }
109            boolean includeHeaders = false;
110            boolean excludeHeaders = false;
111            for (Object o : it) {
112                String pattern = o.toString();
113                if ("headers".equals(pattern) || "+headers".equals(pattern)) {
114                    includeHeaders = true;
115                } else if ("-headers".equals(pattern)) {
116                    excludeHeaders = true;
117                }
118            }
119            if (includeHeaders && excludeHeaders) {
120                throw new IllegalArgumentException("Cannot have both include and exclude headers at the same time in the filter: " + filter);
121            }
122            boolean includeHeader = false;
123            boolean excludeHeader = false;
124            for (Object o : it) {
125                String pattern = o.toString();
126                if (pattern.startsWith("header:") || pattern.startsWith("+header:")) {
127                    includeHeader = true;
128                } else if (pattern.startsWith("-header:")) {
129                    excludeHeader = true;
130                }
131            }
132            if (includeHeader && excludeHeader) {
133                throw new IllegalArgumentException("Cannot have both include and exclude header at the same time in the filter: " + filter);
134            }
135        }
136
137        return claim;
138    }
139
140    private AggregationStrategy createAggregationStrategy(RouteContext routeContext) {
141        AggregationStrategy strategy = getAggregationStrategy();
142        if (strategy == null && aggregationStrategyRef != null) {
143            Object aggStrategy = routeContext.lookup(aggregationStrategyRef, Object.class);
144            if (aggStrategy instanceof AggregationStrategy) {
145                strategy = (AggregationStrategy) aggStrategy;
146            } else if (aggStrategy != null) {
147                strategy = new AggregationStrategyBeanAdapter(aggStrategy, getAggregationStrategyMethodName());
148            } else {
149                throw new IllegalArgumentException("Cannot find AggregationStrategy in Registry with name: " + aggregationStrategyRef);
150            }
151        }
152
153        if (strategy instanceof CamelContextAware) {
154            ((CamelContextAware) strategy).setCamelContext(routeContext.getCamelContext());
155        }
156
157        return strategy;
158    }
159
160    // Fluent API
161    //-------------------------------------------------------------------------
162
163    /**
164     * The claim check operation to use.
165     * The following operations is supported:
166     * <ul>
167     *     <li>Get</li> - Gets (does not remove) the claim check by the given key.
168     *     <li>GetAndRemove</li> - Gets and remove the claim check by the given key.
169     *     <li>Set</li> - Sets a new (will override if key already exists) claim check with the given key.
170     *     <li>Push</li> - Sets a new claim check on the stack (does not use key).
171     *     <li>Pop</li> - Gets the latest claim check from the stack (does not use key).
172     * </ul>
173     */
174    public ClaimCheckDefinition operation(ClaimCheckOperation operation) {
175        setOperation(operation);
176        return this;
177    }
178
179    /**
180     * To use a specific key for claim check id.
181     */
182    public ClaimCheckDefinition key(String key) {
183        setKey(key);
184        return this;
185    }
186
187    /**
188     * Specified a filter to control what data gets merging data back from the claim check repository.
189     *
190     * The following syntax is supported:
191     * <ul>
192     *     <li>body</li> - to aggregate the message body
193     *     <li>attachments</li> - to aggregate all the message attachments
194     *     <li>headers</li> - to aggregate all the message headers
195     *     <li>header:pattern</li> - to aggregate all the message headers that matches the pattern.
196     *     The pattern syntax is documented by: {@link EndpointHelper#matchPattern(String, String)}.
197     * </ul>
198     * You can specify multiple rules separated by comma. For example to include the message body and all headers starting with foo
199     * <tt>body,header:foo*</tt>.
200     * The syntax supports the following prefixes which can be used to specify include,exclude, or remove
201     * <ul>
202     *     <li>+</li> - to include (which is the default mode)
203     *     <li>-</li> - to exclude (exclude takes precedence over include)
204     *     <li>--</li> - to remove (remove takes precedence)
205     * </ul>
206     * For example to exclude a header name foo, and remove all headers starting with bar
207     * <tt>-header:foo,--headers:bar*</tt>
208     * Note you cannot have both include and exclude <tt>header:pattern</tt> at the same time.
209     */
210    public ClaimCheckDefinition filter(String filter) {
211        setFilter(filter);
212        return this;
213    }
214
215    /**
216     * To use a custom {@link AggregationStrategy} instead of the default implementation.
217     * Notice you cannot use both custom aggregation strategy and configure data at the same time.
218     */
219    public ClaimCheckDefinition aggregationStrategy(AggregationStrategy aggregationStrategy) {
220        setAggregationStrategy(aggregationStrategy);
221        return this;
222    }
223
224    /**
225     * To use a custom {@link AggregationStrategy} instead of the default implementation.
226     * Notice you cannot use both custom aggregation strategy and configure data at the same time.
227     */
228    public ClaimCheckDefinition aggregationStrategyRef(String aggregationStrategyRef) {
229        setAggregationStrategyRef(aggregationStrategyRef);
230        return this;
231    }
232
233    /**
234     * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy.
235     */
236    public ClaimCheckDefinition aggregationStrategyMethodName(String aggregationStrategyMethodName) {
237        setAggregationStrategyMethodName(aggregationStrategyMethodName);
238        return this;
239    }
240
241    // Properties
242    //-------------------------------------------------------------------------
243
244    public String getKey() {
245        return key;
246    }
247
248    public void setKey(String key) {
249        this.key = key;
250    }
251
252    public ClaimCheckOperation getOperation() {
253        return operation;
254    }
255
256    public void setOperation(ClaimCheckOperation operation) {
257        this.operation = operation;
258    }
259
260    public String getFilter() {
261        return filter;
262    }
263
264    public void setFilter(String filter) {
265        this.filter = filter;
266    }
267
268    public String getAggregationStrategyRef() {
269        return aggregationStrategyRef;
270    }
271
272    public void setAggregationStrategyRef(String aggregationStrategyRef) {
273        this.aggregationStrategyRef = aggregationStrategyRef;
274    }
275
276    public String getAggregationStrategyMethodName() {
277        return aggregationStrategyMethodName;
278    }
279
280    public void setAggregationStrategyMethodName(String aggregationStrategyMethodName) {
281        this.aggregationStrategyMethodName = aggregationStrategyMethodName;
282    }
283
284    public AggregationStrategy getAggregationStrategy() {
285        return aggregationStrategy;
286    }
287
288    public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
289        this.aggregationStrategy = aggregationStrategy;
290    }
291}