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