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
091        // register endpoint callback so we can proxy the endpoint
092        routeContext.getCamelContext().addRegisterEndpointCallback(new EndpointStrategy() {
093            public Endpoint registerEndpoint(String uri, Endpoint endpoint) {
094                if (endpoint instanceof InterceptSendToEndpoint) {
095                    // endpoint already decorated
096                    return endpoint;
097                } else if (getUri() == null || matchPattern(routeContext.getCamelContext(), uri, getUri())) {
098                    // only proxy if the uri is matched decorate endpoint with our proxy
099                    // should be false by default
100                    boolean skip = getSkipSendToOriginalEndpoint() != null && getSkipSendToOriginalEndpoint();
101                    InterceptSendToEndpoint proxy = new InterceptSendToEndpoint(endpoint, skip);
102                    proxy.setDetour(detour);
103                    return proxy;
104                } else {
105                    // no proxy so return regular endpoint
106                    return endpoint;
107                }
108            }
109        });
110
111
112        // remove the original intercepted route from the outputs as we do not intercept as the regular interceptor
113        // instead we use the proxy endpoints producer do the triggering. That is we trigger when someone sends
114        // an exchange to the endpoint, see InterceptSendToEndpoint for details.
115        RouteDefinition route = routeContext.getRoute();
116        List<ProcessorDefinition<?>> outputs = route.getOutputs();
117        outputs.remove(this);
118
119        return new InterceptEndpointProcessor(uri, detour);
120    }
121
122    /**
123     * Does the uri match the pattern.
124     *
125     * @param camelContext the CamelContext
126     * @param uri the uri
127     * @param pattern the pattern, which can be an endpoint uri as well
128     * @return <tt>true</tt> if matched and we should intercept, <tt>false</tt> if not matched, and not intercept.
129     */
130    protected boolean matchPattern(CamelContext camelContext, String uri, String pattern) {
131        // match using the pattern as-is
132        boolean match = EndpointHelper.matchEndpoint(camelContext, uri, pattern);
133        if (!match) {
134            try {
135                // the pattern could be an uri, so we need to normalize it before matching again
136                pattern = URISupport.normalizeUri(pattern);
137                match = EndpointHelper.matchEndpoint(camelContext, uri, pattern);
138            } catch (Exception e) {
139                // ignore
140            }
141        }
142        return match;
143    }
144
145    /**
146     * Applies this interceptor only if the given predicate is true
147     *
148     * @param predicate  the predicate
149     * @return the builder
150     */
151    public InterceptSendToEndpointDefinition when(Predicate predicate) {
152        WhenDefinition when = new WhenDefinition(predicate);
153        addOutput(when);
154        return this;
155    }
156
157    /**
158     * Skip sending the {@link org.apache.camel.Exchange} to the original intended endpoint
159     *
160     * @return the builder
161     */
162    public InterceptSendToEndpointDefinition skipSendToOriginalEndpoint() {
163        setSkipSendToOriginalEndpoint(Boolean.TRUE);
164        return this;
165    }
166
167    /**
168     * This method is <b>only</b> for handling some post configuration
169     * that is needed since this is an interceptor, and we have to do
170     * a bit of magic logic to fixup to handle predicates
171     * with or without proceed/stop set as well.
172     */
173    public void afterPropertiesSet() {
174        // okay the intercept endpoint works a bit differently than the regular interceptors
175        // so we must fix the route definition yet again
176
177        if (getOutputs().size() == 0) {
178            // no outputs
179            return;
180        }
181
182        // if there is a when definition at first, then its a predicate for this interceptor
183        ProcessorDefinition<?> first = getOutputs().get(0);
184        if (first instanceof WhenDefinition && !(first instanceof WhenSkipSendToEndpointDefinition)) {
185            WhenDefinition when = (WhenDefinition) first;
186
187            // create a copy of when to use as replacement
188            WhenSkipSendToEndpointDefinition newWhen = new WhenSkipSendToEndpointDefinition();
189            newWhen.setExpression(when.getExpression());
190            newWhen.setId(when.getId());
191            newWhen.setInheritErrorHandler(when.isInheritErrorHandler());
192            newWhen.setParent(when.getParent());
193            newWhen.setOtherAttributes(when.getOtherAttributes());
194            newWhen.setDescription(when.getDescription());
195
196            // move this outputs to the when, expect the first one
197            // as the first one is the interceptor itself
198            for (int i = 1; i < outputs.size(); i++) {
199                ProcessorDefinition<?> out = outputs.get(i);
200                newWhen.addOutput(out);
201            }
202            // remove the moved from the original output, by just keeping the first one
203            clearOutput();
204            outputs.add(newWhen);
205        }
206    }
207
208    public Boolean getSkipSendToOriginalEndpoint() {
209        return skipSendToOriginalEndpoint;
210    }
211
212    /**
213     * If set to true then the message is not sent to the original endpoint.
214     * By default (false) the message is both intercepted and then sent to the original endpoint.
215     */
216    public void setSkipSendToOriginalEndpoint(Boolean skipSendToOriginalEndpoint) {
217        this.skipSendToOriginalEndpoint = skipSendToOriginalEndpoint;
218    }
219    
220    public String getUri() {
221        return uri;
222    }
223
224    /**
225     * Intercept sending to the uri or uri pattern.
226     */
227    public void setUri(String uri) {
228        this.uri = uri;
229    }
230}