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 java.util.List;
020
021import javax.xml.bind.annotation.XmlAccessType;
022import javax.xml.bind.annotation.XmlAccessorType;
023import javax.xml.bind.annotation.XmlAttribute;
024import javax.xml.bind.annotation.XmlRootElement;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.Endpoint;
028import org.apache.camel.Predicate;
029import org.apache.camel.Processor;
030import org.apache.camel.impl.InterceptSendToEndpoint;
031import org.apache.camel.processor.InterceptEndpointProcessor;
032import org.apache.camel.spi.AsPredicate;
033import org.apache.camel.spi.EndpointStrategy;
034import org.apache.camel.spi.Metadata;
035import org.apache.camel.spi.RouteContext;
036import org.apache.camel.util.EndpointHelper;
037import org.apache.camel.util.URISupport;
038
039/**
040 * Intercepts messages being sent to an endpoint
041 *
042 * @version 
043 */
044@Metadata(label = "configuration")
045@XmlRootElement(name = "interceptSendToEndpoint")
046@XmlAccessorType(XmlAccessType.FIELD)
047public class InterceptSendToEndpointDefinition extends OutputDefinition<InterceptSendToEndpointDefinition> {
048
049    // TODO: Support lookup endpoint by ref (requires a bit more work)
050
051    // TODO: interceptSendToEndpoint needs to proxy the endpoints at very first
052    // so when other processors uses an endpoint its already proxied, see workaround in SendProcessor
053    // needed when we haven't proxied beforehand. This requires some work in the route builder in Camel
054    // to implement so that should be a part of a bigger rework/improvement in the future
055
056    @XmlAttribute(required = true)
057    private String uri;
058    @XmlAttribute
059    private Boolean skipSendToOriginalEndpoint;
060
061    public InterceptSendToEndpointDefinition() {
062    }
063
064    public InterceptSendToEndpointDefinition(String uri) {
065        this.uri = uri;
066    }
067
068    @Override
069    public String toString() {
070        return "InterceptSendToEndpoint[" + uri + " -> " + getOutputs() + "]";
071    }
072
073    @Override
074    public String getShortName() {
075        return "interceptSendToEndpoint";
076    }
077
078    @Override
079    public String getLabel() {
080        return "interceptSendToEndpoint[" + uri + "]";
081    }
082
083    @Override
084    public boolean isAbstract() {
085        return true;
086    }
087
088    @Override
089    public boolean isTopLevelOnly() {
090        return true;
091    }
092
093    @Override
094    public Processor createProcessor(final RouteContext routeContext) throws Exception {
095        // create the detour
096        final Processor detour = this.createChildProcessor(routeContext, true);
097        final String matchURI = getUri();
098
099        // register endpoint callback so we can proxy the endpoint
100        routeContext.getCamelContext().addRegisterEndpointCallback(new EndpointStrategy() {
101            public Endpoint registerEndpoint(String uri, Endpoint endpoint) {
102                if (endpoint instanceof InterceptSendToEndpoint) {
103                    // endpoint already decorated
104                    return endpoint;
105                } else if (matchURI == null || matchPattern(routeContext.getCamelContext(), uri, matchURI)) {
106                    // only proxy if the uri is matched decorate endpoint with our proxy
107                    // should be false by default
108                    boolean skip = getSkipSendToOriginalEndpoint() != null && getSkipSendToOriginalEndpoint();
109                    InterceptSendToEndpoint proxy = new InterceptSendToEndpoint(endpoint, skip);
110                    proxy.setDetour(detour);
111                    return proxy;
112                } else {
113                    // no proxy so return regular endpoint
114                    return endpoint;
115                }
116            }
117        });
118
119
120        // remove the original intercepted route from the outputs as we do not intercept as the regular interceptor
121        // instead we use the proxy endpoints producer do the triggering. That is we trigger when someone sends
122        // an exchange to the endpoint, see InterceptSendToEndpoint for details.
123        RouteDefinition route = routeContext.getRoute();
124        List<ProcessorDefinition<?>> outputs = route.getOutputs();
125        outputs.remove(this);
126
127        return new InterceptEndpointProcessor(matchURI, detour);
128    }
129
130    /**
131     * Does the uri match the pattern.
132     *
133     * @param camelContext the CamelContext
134     * @param uri the uri
135     * @param pattern the pattern, which can be an endpoint uri as well
136     * @return <tt>true</tt> if matched and we should intercept, <tt>false</tt> if not matched, and not intercept.
137     */
138    protected boolean matchPattern(CamelContext camelContext, String uri, String pattern) {
139        // match using the pattern as-is
140        boolean match = EndpointHelper.matchEndpoint(camelContext, uri, pattern);
141        if (!match) {
142            try {
143                // the pattern could be an uri, so we need to normalize it before matching again
144                pattern = URISupport.normalizeUri(pattern);
145                match = EndpointHelper.matchEndpoint(camelContext, uri, pattern);
146            } catch (Exception e) {
147                // ignore
148            }
149        }
150        return match;
151    }
152
153    /**
154     * Applies this interceptor only if the given predicate is true
155     *
156     * @param predicate  the predicate
157     * @return the builder
158     */
159    public InterceptSendToEndpointDefinition when(@AsPredicate Predicate predicate) {
160        WhenDefinition when = new WhenDefinition(predicate);
161        addOutput(when);
162        return this;
163    }
164
165    /**
166     * Skip sending the {@link org.apache.camel.Exchange} to the original intended endpoint
167     *
168     * @return the builder
169     */
170    public InterceptSendToEndpointDefinition skipSendToOriginalEndpoint() {
171        setSkipSendToOriginalEndpoint(Boolean.TRUE);
172        return this;
173    }
174
175    /**
176     * This method is <b>only</b> for handling some post configuration
177     * that is needed since this is an interceptor, and we have to do
178     * a bit of magic logic to fixup to handle predicates
179     * with or without proceed/stop set as well.
180     */
181    public void afterPropertiesSet() {
182        // okay the intercept endpoint works a bit differently than the regular interceptors
183        // so we must fix the route definition yet again
184
185        if (getOutputs().size() == 0) {
186            // no outputs
187            return;
188        }
189
190        // if there is a when definition at first, then its a predicate for this interceptor
191        ProcessorDefinition<?> first = getOutputs().get(0);
192        if (first instanceof WhenDefinition && !(first instanceof WhenSkipSendToEndpointDefinition)) {
193            WhenDefinition when = (WhenDefinition) first;
194
195            // create a copy of when to use as replacement
196            WhenSkipSendToEndpointDefinition newWhen = new WhenSkipSendToEndpointDefinition();
197            newWhen.setExpression(when.getExpression());
198            newWhen.setId(when.getId());
199            newWhen.setInheritErrorHandler(when.isInheritErrorHandler());
200            newWhen.setParent(when.getParent());
201            newWhen.setOtherAttributes(when.getOtherAttributes());
202            newWhen.setDescription(when.getDescription());
203
204            // move this outputs to the when, expect the first one
205            // as the first one is the interceptor itself
206            for (int i = 1; i < outputs.size(); i++) {
207                ProcessorDefinition<?> out = outputs.get(i);
208                newWhen.addOutput(out);
209            }
210            // remove the moved from the original output, by just keeping the first one
211            clearOutput();
212            outputs.add(newWhen);
213        }
214    }
215
216    public Boolean getSkipSendToOriginalEndpoint() {
217        return skipSendToOriginalEndpoint;
218    }
219
220    /**
221     * If set to true then the message is not sent to the original endpoint.
222     * By default (false) the message is both intercepted and then sent to the original endpoint.
223     */
224    public void setSkipSendToOriginalEndpoint(Boolean skipSendToOriginalEndpoint) {
225        this.skipSendToOriginalEndpoint = skipSendToOriginalEndpoint;
226    }
227    
228    public String getUri() {
229        return uri;
230    }
231
232    /**
233     * Intercept sending to the uri or uri pattern.
234     */
235    public void setUri(String uri) {
236        this.uri = uri;
237    }
238}