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