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.ArrayList;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.camel.CamelContext;
025import org.apache.camel.CamelContextAware;
026import org.apache.camel.Consumer;
027import org.apache.camel.Endpoint;
028import org.apache.camel.Exchange;
029import org.apache.camel.Producer;
030import org.apache.camel.Route;
031import org.apache.camel.Service;
032import org.apache.camel.ServiceStatus;
033import org.apache.camel.StatefulService;
034import org.apache.camel.StaticService;
035import org.apache.camel.component.rest.RestApiEndpoint;
036import org.apache.camel.component.rest.RestEndpoint;
037import org.apache.camel.spi.RestConfiguration;
038import org.apache.camel.spi.RestRegistry;
039import org.apache.camel.support.LifecycleStrategySupport;
040import org.apache.camel.support.ServiceSupport;
041import org.apache.camel.util.ObjectHelper;
042
043public class DefaultRestRegistry extends ServiceSupport implements StaticService, RestRegistry, CamelContextAware {
044
045    private CamelContext camelContext;
046    private final Map<Consumer, RestService> registry = new LinkedHashMap<>();
047    private transient Producer apiProducer;
048
049    public void addRestService(Consumer consumer, String url, String baseUrl, String basePath, String uriTemplate, String method,
050                               String consumes, String produces, String inType, String outType, String routeId, String description) {
051        RestServiceEntry entry = new RestServiceEntry(consumer, url, baseUrl, basePath, uriTemplate, method, consumes, produces, inType, outType, routeId, description);
052        registry.put(consumer, entry);
053    }
054
055    public void removeRestService(Consumer consumer) {
056        registry.remove(consumer);
057    }
058
059    @Override
060    public List<RestRegistry.RestService> listAllRestServices() {
061        return new ArrayList<>(registry.values());
062    }
063
064    @Override
065    public int size() {
066        return registry.size();
067    }
068
069    @Override
070    public String apiDocAsJson() {
071        // see if there is a rest-api endpoint which would be the case if rest api-doc has been explicit enabled
072        if (apiProducer == null) {
073            Endpoint restApiEndpoint = null;
074            Endpoint restEndpoint = null;
075            for (Map.Entry<String, Endpoint> entry : camelContext.getEndpointMap().entrySet()) {
076                String uri = entry.getKey();
077                if (uri.startsWith("rest-api:")) {
078                    restApiEndpoint = entry.getValue();
079                    break;
080                } else if (restEndpoint == null && uri.startsWith("rest:")) {
081                    restEndpoint = entry.getValue();
082                }
083            }
084
085            if (restApiEndpoint == null && restEndpoint != null) {
086                // no rest-api has been explicit enabled, then we need to create it first
087                RestEndpoint rest = (RestEndpoint) restEndpoint;
088                String componentName = rest.getComponentName();
089
090                if (componentName != null) {
091                    RestConfiguration config = camelContext.getRestConfiguration(componentName, true);
092                    String apiComponent = config.getApiComponent() != null ? config.getApiComponent() : RestApiEndpoint.DEFAULT_API_COMPONENT_NAME;
093                    String path = config.getApiContextPath() != null ? config.getApiContextPath() : "api-doc";
094                    restApiEndpoint = camelContext.getEndpoint(String.format("rest-api:%s/%s?componentName=%s&apiComponentName=%s&contextIdPattern=#name#", 
095                        path, camelContext.getName(), componentName, apiComponent));
096                }
097            }
098
099            if (restApiEndpoint != null) {
100                // reuse the producer to avoid creating it
101                try {
102                    apiProducer = restApiEndpoint.createProducer();
103                    camelContext.addService(apiProducer, true);
104                } catch (Exception e) {
105                    throw ObjectHelper.wrapRuntimeCamelException(e);
106                }
107            }
108        }
109
110        if (apiProducer != null) {
111            try {
112                Exchange dummy = apiProducer.getEndpoint().createExchange();
113                apiProducer.process(dummy);
114
115                String json = dummy.hasOut() ? dummy.getOut().getBody(String.class) : dummy.getIn().getBody(String.class);
116                return json;
117            } catch (Exception e) {
118                throw ObjectHelper.wrapRuntimeCamelException(e);
119            }
120        }
121
122        return null;
123    }
124
125    public CamelContext getCamelContext() {
126        return camelContext;
127    }
128
129    public void setCamelContext(CamelContext camelContext) {
130        this.camelContext = camelContext;
131    }
132
133    @Override
134    protected void doStart() throws Exception {
135        ObjectHelper.notNull(camelContext, "camelContext", this);
136        // add a lifecycle so we can keep track when consumers is being removed, so we can unregister them from our registry
137        camelContext.addLifecycleStrategy(new RemoveRestServiceLifecycleStrategy());
138    }
139
140    @Override
141    protected void doStop() throws Exception {
142        registry.clear();
143    }
144
145    /**
146     * Represents a rest service
147     */
148    private final class RestServiceEntry implements RestService {
149
150        private final Consumer consumer;
151        private final String url;
152        private final String baseUrl;
153        private final String basePath;
154        private final String uriTemplate;
155        private final String method;
156        private final String consumes;
157        private final String produces;
158        private final String inType;
159        private final String outType;
160        private final String routeId;
161        private final String description;
162
163        private RestServiceEntry(Consumer consumer, String url, String baseUrl, String basePath, String uriTemplate, String method,
164                                 String consumes, String produces, String inType, String outType, String routeId, String description) {
165            this.consumer = consumer;
166            this.url = url;
167            this.baseUrl = baseUrl;
168            this.basePath = basePath;
169            this.uriTemplate = uriTemplate;
170            this.method = method;
171            this.consumes = consumes;
172            this.produces = produces;
173            this.inType = inType;
174            this.outType = outType;
175            this.routeId = routeId;
176            this.description = description;
177        }
178
179        public Consumer getConsumer() {
180            return consumer;
181        }
182
183        public String getUrl() {
184            return url;
185        }
186
187        public String getBaseUrl() {
188            return baseUrl;
189        }
190
191        public String getBasePath() {
192            return basePath;
193        }
194
195        public String getUriTemplate() {
196            return uriTemplate;
197        }
198
199        public String getMethod() {
200            return method;
201        }
202
203        public String getConsumes() {
204            return consumes;
205        }
206
207        public String getProduces() {
208            return produces;
209        }
210
211        public String getInType() {
212            return inType;
213        }
214
215        public String getOutType() {
216            return outType;
217        }
218
219        public String getState() {
220            // must use String type to be sure remote JMX can read the attribute without requiring Camel classes.
221            ServiceStatus status = null;
222            if (consumer instanceof StatefulService) {
223                status = ((StatefulService) consumer).getStatus();
224            }
225            // if no status exists then its stopped
226            if (status == null) {
227                status = ServiceStatus.Stopped;
228            }
229            return status.name();
230        }
231
232        public String getRouteId() {
233            return routeId;
234        }
235
236        public String getDescription() {
237            return description;
238        }
239    }
240
241    /**
242     * A {@link org.apache.camel.spi.LifecycleStrategy} that keeps track when a {@link Consumer} is removed
243     * and automatic un-register it from this REST registry.
244     */
245    private final class RemoveRestServiceLifecycleStrategy extends LifecycleStrategySupport {
246
247        @Override
248        public void onServiceRemove(CamelContext context, Service service, Route route) {
249            super.onServiceRemove(context, service, route);
250
251            // if its a consumer then de-register it from the rest registry
252            if (service instanceof Consumer) {
253                removeRestService((Consumer) service);
254            }
255        }
256    }
257}