All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.util.toolbox.FlexibleAggregationStrategy Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.util.toolbox;

import java.util.Collection;

import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
import org.apache.camel.TypeConversionException;
import org.apache.camel.builder.ExpressionBuilder;
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.apache.camel.processor.aggregate.CompletionAwareAggregationStrategy;
import org.apache.camel.processor.aggregate.TimeoutAwareAggregationStrategy;
import org.apache.camel.util.ExchangeHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The Flexible Aggregation Strategy is a highly customizable, fluently configurable aggregation strategy. It allows you to quickly 
 * allows you to quickly whip up an {@link AggregationStrategy} that is capable of performing the most typical aggregation duties, 
 * with zero Java code. 
 * 

* It can perform the following logic: *

    *
  • Filtering results based on a defined {@link Predicate} written in any language, such as XPath, OGNL, Simple, Javascript, etc.
  • *
  • Picking specific data elements for aggregation.
  • *
  • Accumulating results in any designated {@link Collection} type, e.g. in a HashSet, LinkedList, ArrayList, etc.
  • *
  • Storing the output in a specific place in the Exchange: a property, a header or in the body.
  • *
* * It also includes the ability to specify both aggregation batch completion actions and timeout actions, in an abbreviated manner. *

* This Aggregation Strategy is suitable for usage in aggregate, split, multicast, enrich and recipient list EIPs. * */ public class FlexibleAggregationStrategy implements AggregationStrategy, CompletionAwareAggregationStrategy, TimeoutAwareAggregationStrategy { private static final Logger LOG = LoggerFactory.getLogger(FlexibleAggregationStrategy.class); private Expression pickExpression = ExpressionBuilder.bodyExpression(); private Predicate conditionPredicate; @SuppressWarnings("rawtypes") private Class collectionType; @SuppressWarnings("unchecked") private Class castAs = (Class) Object.class; private boolean storeNulls; private boolean ignoreInvalidCasts; // = false private FlexibleAggregationStrategyInjector injector = new BodyInjector(castAs); private TimeoutAwareMixin timeoutMixin; private CompletionAwareMixin completionMixin; /** * Initializes a new instance with {@link Object} as the {@link FlexibleAggregationStrategy#castAs} type. */ public FlexibleAggregationStrategy() { } /** * Initializes a new instance with the specified type as the {@link FlexibleAggregationStrategy#castAs} type. * @param type The castAs type. */ public FlexibleAggregationStrategy(Class type) { this.castAs = type; } /** * Set an expression to extract the element to be aggregated from the incoming {@link Exchange}. * All results are cast to the {@link FlexibleAggregationStrategy#castAs} type (or the type specified in the constructor). *

* By default, it picks the full IN message body of the incoming exchange. * @param expression The picking expression. * @return This instance. */ public FlexibleAggregationStrategy pick(Expression expression) { this.pickExpression = expression; return this; } /** * Set a filter condition such as only results satisfying it will be aggregated. * By default, all picked values will be processed. * @param predicate The condition. * @return This instance. */ public FlexibleAggregationStrategy condition(Predicate predicate) { this.conditionPredicate = predicate; return this; } /** * Accumulate the result of the pick expression in a collection of the designated type. * No nulls will stored unless the {@link FlexibleAggregationStrategy#storeNulls()} option is enabled. * @param collectionType The type of the Collection to aggregate into. * @return This instance. */ @SuppressWarnings("rawtypes") public FlexibleAggregationStrategy accumulateInCollection(Class collectionType) { this.collectionType = collectionType; return this; } /** * Store the result of this Aggregation Strategy (whether an atomic element or a Collection) in a property with * the designated name. * @param propertyName The property name. * @return This instance. */ public FlexibleAggregationStrategy storeInProperty(String propertyName) { this.injector = new PropertyInjector(castAs, propertyName); return this; } /** * Store the result of this Aggregation Strategy (whether an atomic element or a Collection) in an IN message header with * the designated name. * @param headerName The header name. * @return This instance. */ public FlexibleAggregationStrategy storeInHeader(String headerName) { this.injector = new HeaderInjector(castAs, headerName); return this; } /** * Store the result of this Aggregation Strategy (whether an atomic element or a Collection) in the body of the IN message. * @return This instance. */ public FlexibleAggregationStrategy storeInBody() { this.injector = new BodyInjector(castAs); return this; } /** * Cast the result of the pick expression to this type. * @param castAs Type for the cast. * @return This instance. */ public FlexibleAggregationStrategy castAs(Class castAs) { this.castAs = castAs; injector.setType(castAs); return this; } /** * Enables storing null values in the resulting collection. * By default, this aggregation strategy will drop null values. * @return This instance. */ public FlexibleAggregationStrategy storeNulls() { this.storeNulls = true; return this; } /** * Ignores invalid casts instead of throwing an exception if the pick expression result cannot be casted to the * specified type. * By default, this aggregation strategy will throw an exception if an invalid cast occurs. * @return This instance. */ public FlexibleAggregationStrategy ignoreInvalidCasts() { this.ignoreInvalidCasts = true; return this; } /** * Plugs in logic to execute when a timeout occurs. * @param timeoutMixin * @return This instance. */ public FlexibleAggregationStrategy timeoutAware(TimeoutAwareMixin timeoutMixin) { this.timeoutMixin = timeoutMixin; return this; } /** * Plugs in logic to execute when an aggregation batch completes. * @param completionMixin * @return This instance. */ public FlexibleAggregationStrategy completionAware(CompletionAwareMixin completionMixin) { this.completionMixin = completionMixin; return this; } @Override public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { Exchange exchange = oldExchange; if (exchange == null) { exchange = ExchangeHelper.createCorrelatedCopy(newExchange, true); injector.prepareAggregationExchange(exchange); } // 1. Apply the condition and reject the aggregation if unmatched if (conditionPredicate != null && !conditionPredicate.matches(newExchange)) { LOG.trace("Dropped exchange {} from aggregation as predicate {} was not matched", newExchange, conditionPredicate); return exchange; } // 2. Pick the appropriate element of the incoming message, casting it to the specified class // If null, act accordingly based on storeNulls E picked = null; try { picked = pickExpression.evaluate(newExchange, castAs); } catch (TypeConversionException exception) { if (!ignoreInvalidCasts) { throw exception; } } if (picked == null && !storeNulls) { LOG.trace("Dropped exchange {} from aggregation as pick expression returned null and storing nulls is not enabled", newExchange); return exchange; } if (collectionType == null) { injectAsRawValue(exchange, picked); } else { injectAsCollection(exchange, picked, collectionType); } return exchange; } @Override public void timeout(Exchange oldExchange, int index, int total, long timeout) { if (timeoutMixin == null) { return; } timeoutMixin.timeout(oldExchange, index, total, timeout); } @Override public void onCompletion(Exchange exchange) { if (completionMixin == null) { return; } completionMixin.onCompletion(exchange); } private void injectAsRawValue(Exchange oldExchange, E picked) { injector.setValue(oldExchange, picked); } private void injectAsCollection(Exchange oldExchange, E picked, Class collectionType) { Collection col = injector.getValueAsCollection(oldExchange, collectionType); col = safeInsertIntoCollection(oldExchange, col, picked); injector.setValueAsCollection(oldExchange, col); } @SuppressWarnings("unchecked") private Collection safeInsertIntoCollection(Exchange oldExchange, Collection oldValue, E toInsert) { Collection collection = null; try { if (oldValue == null || oldExchange.getProperty(Exchange.AGGREGATED_COLLECTION_GUARD, Boolean.class) == null) { try { collection = collectionType.newInstance(); } catch (Exception e) { LOG.warn("Could not instantiate collection of type {}. Aborting aggregation.", collectionType); throw ObjectHelper.wrapCamelExecutionException(oldExchange, e); } oldExchange.setProperty(Exchange.AGGREGATED_COLLECTION_GUARD, Boolean.FALSE); } else { collection = collectionType.cast(oldValue); } if (collection != null) { collection.add(toInsert); } } catch (ClassCastException exception) { if (!ignoreInvalidCasts) { throw exception; } } return collection; } public interface TimeoutAwareMixin { void timeout(Exchange exchange, int index, int total, long timeout); } public interface CompletionAwareMixin { void onCompletion(Exchange exchange); } private abstract class FlexibleAggregationStrategyInjector { protected Class type; FlexibleAggregationStrategyInjector(Class type) { this.type = type; } public void setType(Class type) { this.type = type; } public abstract void prepareAggregationExchange(Exchange exchange); public abstract E getValue(Exchange exchange); public abstract void setValue(Exchange exchange, E obj); public abstract Collection getValueAsCollection(Exchange exchange, Class type); public abstract void setValueAsCollection(Exchange exchange, Collection obj); } private class PropertyInjector extends FlexibleAggregationStrategyInjector { private String propertyName; PropertyInjector(Class type, String propertyName) { super(type); this.propertyName = propertyName; } @Override public void prepareAggregationExchange(Exchange exchange) { exchange.removeProperty(propertyName); } @Override public E getValue(Exchange exchange) { return exchange.getProperty(propertyName, type); } @Override public void setValue(Exchange exchange, E obj) { exchange.setProperty(propertyName, obj); } @Override @SuppressWarnings("unchecked") public Collection getValueAsCollection(Exchange exchange, Class type) { Object value = exchange.getProperty(propertyName); if (value == null) { // empty so create a new collection to host this return exchange.getContext().getInjector().newInstance(type); } else { return exchange.getProperty(propertyName, type); } } @Override public void setValueAsCollection(Exchange exchange, Collection obj) { exchange.setProperty(propertyName, obj); } } private class HeaderInjector extends FlexibleAggregationStrategyInjector { private String headerName; HeaderInjector(Class type, String headerName) { super(type); this.headerName = headerName; } @Override public void prepareAggregationExchange(Exchange exchange) { exchange.getIn().removeHeader(headerName); } @Override public E getValue(Exchange exchange) { return exchange.getIn().getHeader(headerName, type); } @Override public void setValue(Exchange exchange, E obj) { exchange.getIn().setHeader(headerName, obj); } @Override @SuppressWarnings("unchecked") public Collection getValueAsCollection(Exchange exchange, Class type) { Object value = exchange.getIn().getHeader(headerName); if (value == null) { // empty so create a new collection to host this return exchange.getContext().getInjector().newInstance(type); } else { return exchange.getIn().getHeader(headerName, type); } } @Override public void setValueAsCollection(Exchange exchange, Collection obj) { exchange.getIn().setHeader(headerName, obj); } } private class BodyInjector extends FlexibleAggregationStrategyInjector { BodyInjector(Class type) { super(type); } @Override public void prepareAggregationExchange(Exchange exchange) { exchange.getIn().setBody(null); } @Override public E getValue(Exchange exchange) { return exchange.getIn().getBody(type); } @Override public void setValue(Exchange exchange, E obj) { exchange.getIn().setBody(obj); } @Override @SuppressWarnings("unchecked") public Collection getValueAsCollection(Exchange exchange, Class type) { Object value = exchange.getIn().getBody(); if (value == null) { // empty so create a new collection to host this return exchange.getContext().getInjector().newInstance(type); } else { return exchange.getIn().getBody(type); } } @Override public void setValueAsCollection(Exchange exchange, Collection obj) { exchange.getIn().setBody(obj); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy