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.impl;
018
019import java.util.List;
020import java.util.Optional;
021
022import org.apache.camel.NamedNode;
023import org.apache.camel.model.FromDefinition;
024import org.apache.camel.model.RouteDefinition;
025import org.apache.camel.model.rest.RestDefinition;
026import org.apache.camel.model.rest.VerbDefinition;
027import org.apache.camel.spi.NodeIdFactory;
028
029/**
030 * Factory for generating route ids based on uris.
031 * <p>
032 * For direct/seda routes it returns route name (direct:start -> start).
033 * For rest routes it returns its method and context path formatted as one string.
034 * <p>
035 * When id cannot be generated, falls back to other {@link NodeIdFactory} implementation.
036 * If none is passed in the constructor, then {@link DefaultNodeIdFactory} is used.
037 */
038public class RouteIdFactory implements NodeIdFactory {
039
040    private static final char SEPARATOR = '-';
041    private NodeIdFactory defaultNodeIdFactory;
042
043    public RouteIdFactory() {
044        defaultNodeIdFactory = new DefaultNodeIdFactory();
045    }
046
047    public RouteIdFactory(NodeIdFactory defaultNodeIdFactory) {
048        this.defaultNodeIdFactory = defaultNodeIdFactory;
049    }
050
051    @Override
052    public String createId(NamedNode definition) {
053        if (definition instanceof RouteDefinition) {
054            Optional<String> id = extractId((RouteDefinition) definition);
055
056            if (id.isPresent()) {
057                return id.get();
058            }
059
060            id = extractIdFromRestDefinition((RouteDefinition) definition);
061
062            if (id.isPresent()) {
063                return id.get();
064            }
065        }
066
067        if (definition instanceof VerbDefinition) {
068            Optional<String> id = extractIdFromVerb((VerbDefinition) definition);
069
070            if (id.isPresent()) {
071                return id.get();
072            }
073        }
074
075        return defaultNodeIdFactory.createId(definition);
076    }
077
078    /**
079     * Extract id from routes
080     */
081    private Optional<String> extractId(RouteDefinition routeDefinition) {
082        if (routeDefinition.getRestDefinition() != null) {
083            return Optional.empty();
084        }
085
086        List<FromDefinition> inputs = routeDefinition.getInputs();
087
088        if (inputs == null || inputs.isEmpty()) {
089            return Optional.empty();
090        }
091
092        FromDefinition from = inputs.get(0);
093        String uri = from.getUri();
094
095        // we want to use the context-path of the route
096        int colon = uri.indexOf(':');
097
098        if (colon > 0) {
099            String name = uri.substring(colon + 1);
100
101            int questionMark = name.indexOf("?");
102
103            if (questionMark > 0) {
104                return Optional.of(name.substring(0, questionMark));
105            } else {
106                return Optional.of(name);
107            }
108        }
109
110        return Optional.empty();
111    }
112
113    /**
114     * Extract id from a rest route.
115     */
116    private Optional<String> extractIdFromRestDefinition(RouteDefinition route) {
117        if (route.getRestDefinition() != null) {
118            return extractIdFromInput(route);
119        }
120
121        return Optional.empty();
122    }
123
124    /**
125     * Extract id from a rest verb definition.
126     */
127    private Optional<String> extractIdFromVerb(VerbDefinition verb) {
128        RestDefinition restDefinition = verb.getRest();
129
130        if (restDefinition != null) {
131            StringBuilder routeId = new StringBuilder();
132            routeId.append(verb.asVerb());
133            appendWithSeparator(routeId, prepareUri(restDefinition.getPath()));
134
135            if (verb.getUri() != null && verb.getUri().length() > 0) {
136                appendWithSeparator(routeId, prepareUri(verb.getUri()));
137            }
138
139            verb.setUsedForGeneratingNodeId(true);
140
141            return Optional.of(routeId.toString());
142        }
143
144        return Optional.empty();
145
146    }
147
148    /**
149     * Extract id from rest input uri.
150     */
151    private Optional<String> extractIdFromInput(RouteDefinition route) {
152        List<FromDefinition> inputs = route.getInputs();
153
154        if (inputs == null || inputs.isEmpty()) {
155            return Optional.empty();
156        }
157
158        FromDefinition from = inputs.get(0);
159        String uri = from.getUri();
160
161        String[] uriSplitted = uri.split(":");
162
163        // needs to have at least 3 fields
164        if (uriSplitted.length < 3) {
165            return Optional.empty();
166        }
167
168        String verb = uriSplitted[1];
169        String contextPath = uriSplitted[2];
170        String additionalUri = "";
171
172        if (uriSplitted.length > 3 && uriSplitted[3].startsWith("/")) {
173            additionalUri = uriSplitted[3];
174        }
175
176        StringBuilder routeId = new StringBuilder(verb.length() + contextPath.length() + additionalUri.length());
177
178        routeId.append(verb);
179        appendWithSeparator(routeId, prepareUri(contextPath));
180
181        if (additionalUri.length() > 0) {
182            appendWithSeparator(routeId, prepareUri(additionalUri));
183        }
184
185        return Optional.of(routeId.toString());
186    }
187
188    /**
189     * Prepares uri to be part of the id.
190     */
191    private String prepareUri(String uri) {
192        if (uri == null) {
193            return "";
194        }
195
196        if (uri.contains("?")) {
197            uri = uri.substring(0, uri.indexOf('?'));
198        }
199
200        return uri.replaceAll("/", String.valueOf(SEPARATOR));
201    }
202
203    /**
204     * Appends new element to the builder.
205     */
206    private void appendWithSeparator(StringBuilder builder, String str) {
207        if (builder.charAt(builder.length() - 1) == SEPARATOR) {
208            if (str.startsWith(String.valueOf(SEPARATOR))) {
209                builder.append(str.replaceFirst(String.valueOf(SEPARATOR), ""));
210            } else {
211                builder.append(str);
212            }
213        } else {
214            if (!str.startsWith(String.valueOf(SEPARATOR))) {
215                builder.append(SEPARATOR);
216            }
217
218            builder.append(str);
219        }
220    }
221}