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