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