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