Repository: camel Updated Branches: refs/heads/master 3b0b54995 -> 1e86a1138
CAMEL-7620: Rest DSL. Enlist rest services in RestRegistry and JMX. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/1e86a113 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/1e86a113 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/1e86a113 Branch: refs/heads/master Commit: 1e86a1138b3f5c7194192c8d1d337ea4239f3ef4 Parents: cafaa83 Author: Claus Ibsen <davscl...@apache.org> Authored: Tue Aug 5 11:18:08 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Tue Aug 5 11:18:26 2014 +0200 ---------------------------------------------------------------------- .../java/org/apache/camel/CamelContext.java | 4 +- .../management/mbean/CamelOpenMBeanTypes.java | 6 +- .../camel/component/rest/RestEndpoint.java | 39 +++++- .../management/mbean/ManagedRestRegistry.java | 8 +- .../model/rest/RestConfigurationDefinition.java | 22 ++++ .../camel/model/rest/RestHostNameResolver.java | 28 +++++ .../org/apache/camel/spi/RestConfiguration.java | 32 +++++ .../java/org/apache/camel/util/HostUtils.java | 119 +++++++++++++++++++ .../org/apache/camel/model/rest/jaxb.index | 1 + .../management/ManagedRestRegistryTest.java | 87 ++++++++++++++ 10 files changed, 335 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/CamelContext.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/CamelContext.java b/camel-core/src/main/java/org/apache/camel/CamelContext.java index 5297031..5b175a3 100644 --- a/camel-core/src/main/java/org/apache/camel/CamelContext.java +++ b/camel-core/src/main/java/org/apache/camel/CamelContext.java @@ -1361,11 +1361,11 @@ public interface CamelContext extends SuspendableService, RuntimeConfiguration { /** * Gets the {@link org.apache.camel.spi.RestRegistry} to use */ - public RestRegistry getRestRegistry(); + RestRegistry getRestRegistry(); /** * Sets a custom {@link org.apache.camel.spi.RestRegistry} to use. */ - public void setRestRegistry(RestRegistry restRegistry); + void setRestRegistry(RestRegistry restRegistry); } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java b/camel-core/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java index 6ef4b81..5339bff 100644 --- a/camel-core/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java +++ b/camel-core/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java @@ -42,12 +42,12 @@ public final class CamelOpenMBeanTypes { public static TabularType listRestServicesTabularType() throws OpenDataException { CompositeType ct = listRestServicesCompositeType(); - return new TabularType("listRestServices", "Lists all the rest services in the registry", ct, new String[]{"url", "path", "verb", "consumes", "produces", "state"}); + return new TabularType("listRestServices", "Lists all the rest services in the registry", ct, new String[]{"url", "method", "uri template", "consumes", "produces", "state"}); } public static CompositeType listRestServicesCompositeType() throws OpenDataException { - return new CompositeType("types", "types", new String[]{"url", "path", "verb", "consumes", "produces", "state"}, - new String[]{"Url", "Path", "Verb", "Consumes", "Produces", "State"}, + return new CompositeType("types", "types", new String[]{"url", "method", "uri template", "consumes", "produces", "state"}, + new String[]{"Url", "Method", "Uri Template", "Consumes", "Produces", "State"}, new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING}); } http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java b/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java index d908452..6e36f53 100644 --- a/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java +++ b/camel-core/src/main/java/org/apache/camel/component/rest/RestEndpoint.java @@ -25,9 +25,12 @@ import org.apache.camel.NoSuchBeanException; import org.apache.camel.Processor; import org.apache.camel.Producer; import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.spi.RestConfiguration; import org.apache.camel.spi.RestConsumerFactory; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; +import org.apache.camel.util.HostUtils; +import org.apache.camel.util.ObjectHelper; @UriEndpoint(scheme = "rest") public class RestEndpoint extends DefaultEndpoint { @@ -154,10 +157,42 @@ public class RestEndpoint extends DefaultEndpoint { Consumer consumer = factory.createConsumer(getCamelContext(), processor, getVerb(), getPath(), getConsumes(), getProduces(), getParameters()); configureConsumer(consumer); + // if no explicit port/host configured, then use port from rest configuration + String scheme = "http"; + String host = ""; + int port = 80; + + RestConfiguration config = getCamelContext().getRestConfiguration(); + if (config.getScheme() != null) { + scheme = config.getScheme(); + } + if (config.getHost() != null) { + host = config.getHost(); + } + int num = config.getPort(); + if (num > 0) { + port = num; + } + + // if no explicit hostname set then resolve the hostname + if (ObjectHelper.isEmpty(host)) { + if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) { + host = HostUtils.getLocalHostName(); + } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) { + host = HostUtils.getLocalIp(); + } + } + + // calculate the url to the rest service + String path = getPath(); + if (!path.startsWith("/")) { + path = "/" + path; + } + String url = scheme + "://" + host + (port != 80 ? ":" + port : "") + path; + // add to rest registry so we can keep track of them, we will remove from the registry when the consumer is removed - // TODO: need to get the absolute url of the service // TODO: need to be able to unregister from the registry - getCamelContext().getRestRegistry().addRestService(consumer, null, getVerb(), getPath(), getConsumes(), getProduces()); + getCamelContext().getRestRegistry().addRestService(consumer, url, getVerb(), getPath(), getConsumes(), getProduces()); return consumer; } else { http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedRestRegistry.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedRestRegistry.java b/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedRestRegistry.java index b5877e4..4bd915e 100644 --- a/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedRestRegistry.java +++ b/camel-core/src/main/java/org/apache/camel/management/mbean/ManagedRestRegistry.java @@ -60,13 +60,13 @@ public class ManagedRestRegistry extends ManagedService implements ManagedRestRe for (RestRegistry.RestService entry : services) { CompositeType ct = CamelOpenMBeanTypes.listRestServicesCompositeType(); String url = entry.getUrl(); - String path = entry.getUriTemplate(); - String verb = entry.getMethod(); + String method = entry.getMethod(); + String uriTemplate = entry.getUriTemplate(); String consumes = entry.getConsumes(); String produces = entry.getProduces(); String state = entry.getState(); - CompositeData data = new CompositeDataSupport(ct, new String[]{"url", "path", "verb", "consumes", "produces", "state"}, - new Object[]{url, path, verb, consumes, produces, state}); + CompositeData data = new CompositeDataSupport(ct, new String[]{"url", "method", "uri template", "consumes", "produces", "state"}, + new Object[]{url, method, uriTemplate, consumes, produces, state}); answer.put(data); } return answer; http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java index 758a099..54378f2 100644 --- a/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java +++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestConfigurationDefinition.java @@ -50,6 +50,9 @@ public class RestConfigurationDefinition { private String port; @XmlAttribute + private RestHostNameResolver hostNameResolver; + + @XmlAttribute private RestBindingMode bindingMode; @XmlAttribute @@ -102,6 +105,14 @@ public class RestConfigurationDefinition { this.port = port; } + public RestHostNameResolver getHostNameResolver() { + return hostNameResolver; + } + + public void setHostNameResolver(RestHostNameResolver hostNameResolver) { + this.hostNameResolver = hostNameResolver; + } + public RestBindingMode getBindingMode() { return bindingMode; } @@ -202,6 +213,14 @@ public class RestConfigurationDefinition { } /** + * To specify the hostname resolver + */ + public RestConfigurationDefinition hostNameResolver(RestHostNameResolver hostNameResolver) { + setHostNameResolver(hostNameResolver); + return this; + } + + /** * To specify the binding mode */ public RestConfigurationDefinition bindingMode(RestBindingMode bindingMode) { @@ -297,6 +316,9 @@ public class RestConfigurationDefinition { if (port != null) { answer.setPort(CamelContextHelper.parseInteger(context, port)); } + if (hostNameResolver != null) { + answer.setRestHostNameResolver(hostNameResolver.name()); + } if (bindingMode != null) { answer.setBindingMode(bindingMode.name()); } http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java b/camel-core/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java new file mode 100644 index 0000000..fd85d08 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.model.rest; + +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlType; + +@XmlType +@XmlEnum(String.class) +public enum RestHostNameResolver { + + localIp, localHostName + +} http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java b/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java index f821036..d3fcb2d 100644 --- a/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java +++ b/camel-core/src/main/java/org/apache/camel/spi/RestConfiguration.java @@ -28,10 +28,15 @@ public class RestConfiguration { auto, off, json, xml, json_xml } + public enum RestHostNameResolver { + localIp, localHostName + } + private String component; private String scheme; private String host; private int port; + private RestHostNameResolver restHostNameResolver = RestHostNameResolver.localHostName; private RestBindingMode bindingMode = RestBindingMode.off; private String jsonDataFormat; private String xmlDataFormat; @@ -113,6 +118,33 @@ public class RestConfiguration { } /** + * Gets the resolver to use for resolving hostname + * + * @return the resolver + */ + public RestHostNameResolver getRestHostNameResolver() { + return restHostNameResolver; + } + + /** + * Sets the resolver to use for resolving hostname + * + * @param restHostNameResolver the resolver + */ + public void setRestHostNameResolver(RestHostNameResolver restHostNameResolver) { + this.restHostNameResolver = restHostNameResolver; + } + + /** + * Sets the resolver to use for resolving hostname + * + * @param restHostNameResolver the resolver + */ + public void setRestHostNameResolver(String restHostNameResolver) { + this.restHostNameResolver = RestHostNameResolver.valueOf(restHostNameResolver); + } + + /** * Gets the binding mode used by the REST consumer * * @return the binding mode http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/java/org/apache/camel/util/HostUtils.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/HostUtils.java b/camel-core/src/main/java/org/apache/camel/util/HostUtils.java new file mode 100644 index 0000000..f8ff6a0 --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/util/HostUtils.java @@ -0,0 +1,119 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.util; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +public final class HostUtils { + + private HostUtils() { + //Utility Class + } + + /** + * Returns a {@link} of {@link InetAddress} per {@link NetworkInterface} as a {@link Map}. + */ + public static Map<String, Set<InetAddress>> getNetworkInterfaceAddresses() { + //JVM returns interfaces in a non-predictable order, so to make this more predictable + //let's have them sort by interface name (by using a TreeMap). + Map<String, Set<InetAddress>> interfaceAddressMap = new TreeMap<String, Set<InetAddress>>(); + try { + Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); + while (ifaces.hasMoreElements()) { + NetworkInterface iface = (NetworkInterface) ifaces.nextElement(); + //We only care about usable non-loopback interfaces. + if (iface.isUp() && !iface.isLoopback() && !iface.isPointToPoint()) { + String name = iface.getName(); + Enumeration<InetAddress> ifaceAdresses = iface.getInetAddresses(); + while (ifaceAdresses.hasMoreElements()) { + InetAddress ia = ifaceAdresses.nextElement(); + //We want to filter out mac addresses + if (!ia.isLoopbackAddress() && !ia.getHostAddress().contains(":")) { + Set<InetAddress> addresses = interfaceAddressMap.get(name); + if (addresses == null) { + addresses = new LinkedHashSet<InetAddress>(); + } + addresses.add(ia); + interfaceAddressMap.put(name, addresses); + } + } + } + } + } catch (SocketException ex) { + //noop + } + return interfaceAddressMap; + } + + /** + * Returns a {@link Set} of {@link InetAddress} that are non-loopback or mac. + */ + public static Set<InetAddress> getAddresses() { + Set<InetAddress> allAddresses = new LinkedHashSet<InetAddress>(); + Map<String, Set<InetAddress>> interfaceAddressMap = getNetworkInterfaceAddresses(); + for (Map.Entry<String, Set<InetAddress>> entry : interfaceAddressMap.entrySet()) { + Set<InetAddress> addresses = entry.getValue(); + if (!addresses.isEmpty()) { + for (InetAddress address : addresses) { + allAddresses.add(address); + } + } + } + return allAddresses; + } + + + /** + * Chooses one of the available {@link InetAddress} based on the specified preference. + */ + private static InetAddress chooseAddress() throws UnknownHostException { + Set<InetAddress> addresses = getAddresses(); + if (addresses.contains(InetAddress.getLocalHost())) { + //Then if local host address is not bound to a loop-back interface, use it. + return InetAddress.getLocalHost(); + } else if (addresses != null && !addresses.isEmpty()) { + //else return the first available addrress + return addresses.toArray(new InetAddress[addresses.size()])[0]; + } else { + //else we are forcedt to use the localhost address. + return InetAddress.getLocalHost(); + } + } + + /** + * Returns the local hostname. It loops through the network interfaces and returns the first non loopback hostname + */ + public static String getLocalHostName() throws UnknownHostException { + return chooseAddress().getHostName(); + } + + /** + * Returns the local IP. It loops through the network interfaces and returns the first non loopback address + */ + public static String getLocalIp() throws UnknownHostException { + return chooseAddress().getHostAddress(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/main/resources/org/apache/camel/model/rest/jaxb.index ---------------------------------------------------------------------- diff --git a/camel-core/src/main/resources/org/apache/camel/model/rest/jaxb.index b/camel-core/src/main/resources/org/apache/camel/model/rest/jaxb.index index a23c2d5..3b0e381 100644 --- a/camel-core/src/main/resources/org/apache/camel/model/rest/jaxb.index +++ b/camel-core/src/main/resources/org/apache/camel/model/rest/jaxb.index @@ -23,6 +23,7 @@ RestBindingDefinition RestBindingMode RestConfigurationDefinition RestDefinition +RestHostNameResolver RestPropertyDefinition RestsDefinition VerbDefinition \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/1e86a113/camel-core/src/test/java/org/apache/camel/management/ManagedRestRegistryTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/management/ManagedRestRegistryTest.java b/camel-core/src/test/java/org/apache/camel/management/ManagedRestRegistryTest.java new file mode 100644 index 0000000..e29b384 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/management/ManagedRestRegistryTest.java @@ -0,0 +1,87 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.management; + +import java.util.Set; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.openmbean.TabularData; + +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.rest.DummyRestConsumerFactory; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.impl.SimpleRegistry; + +public class ManagedRestRegistryTest extends ManagementTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + SimpleRegistry registry = new SimpleRegistry(); + registry.put("dummy-test", new DummyRestConsumerFactory()); + return new DefaultCamelContext(registry); + } + + public void testRestRegistry() throws Exception { + // JMX tests dont work well on AIX CI servers (hangs them) + if (isPlatform("aix")) { + return; + } + + MBeanServer mbeanServer = getMBeanServer(); + + ObjectName on = ObjectName.getInstance("org.apache.camel:context=camel-1,type=services,*"); + + // number of services + Set<ObjectName> names = mbeanServer.queryNames(on, null); + ObjectName name = null; + for (ObjectName service : names) { + if (service.toString().contains("DefaultRestRegistry")) { + name = service; + break; + } + } + assertNotNull("Cannot find DefaultRestRegistry", name); + assertTrue(mbeanServer.isRegistered(name)); + + assertEquals(3, mbeanServer.getAttribute(name, "NumberOfRestServices")); + + TabularData data = (TabularData) mbeanServer.invoke(name, "listRestServices", null, null); + assertEquals(3, data.size()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + rest("/say/hello/{name}") + .get().to("direct:hello"); + + rest("/say/bye") + .get().consumes("application/json").to("direct:bye") + .post().to("mock:update"); + + from("direct:hello") + .transform().simple("Hello ${header.name}"); + + from("direct:bye") + .transform().constant("Bye World"); + } + }; + } +}