You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2012/09/14 16:18:09 UTC

svn commit: r1384795 - in /openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina: ./ routing/

Author: rmannibucau
Date: Fri Sep 14 14:18:09 2012
New Revision: 1384795

URL: http://svn.apache.org/viewvc?rev=1384795&view=rev
Log:
TOMEE-416 basic out of the box routing feature

Added:
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/Route.java
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouteFilter.java
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterException.java
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterInitializer.java
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterValve.java
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/SimpleRouter.java
Modified:
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java
    openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java

Modified: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java?rev=1384795&r1=1384794&r2=1384795&view=diff
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java (original)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java Fri Sep 14 14:18:09 2012
@@ -25,6 +25,7 @@ import org.apache.openejb.assembler.clas
 import org.apache.openejb.loader.SystemInstance;
 import org.apache.openejb.util.LogCategory;
 import org.apache.openejb.util.Logger;
+import org.apache.tomee.catalina.routing.RouterInitializer;
 import org.apache.xbean.finder.util.Classes;
 import org.xml.sax.InputSource;
 
@@ -86,6 +87,10 @@ public class OpenEJBContextConfig extend
 
     @Override
     protected void webConfig() {
+        // routing config
+        context.addServletContainerInitializer(new RouterInitializer(), null); // first one
+
+        // read the real config
         super.webConfig();
 
         // add myfaces auto-initializer

Modified: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java?rev=1384795&r1=1384794&r2=1384795&view=diff
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java (original)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomcatWebAppBuilder.java Fri Sep 14 14:18:09 2012
@@ -89,6 +89,9 @@ import org.apache.tomcat.util.digester.D
 import org.apache.tomee.catalina.cluster.ClusterObserver;
 import org.apache.tomee.catalina.cluster.TomEEClusterListener;
 import org.apache.tomee.catalina.event.AfterApplicationCreated;
+import org.apache.tomee.catalina.routing.RouteFilter;
+import org.apache.tomee.catalina.routing.RouterInitializer;
+import org.apache.tomee.catalina.routing.RouterValve;
 import org.apache.tomee.common.LegacyAnnotationProcessor;
 import org.apache.tomee.common.TomcatVersion;
 import org.apache.tomee.common.UserTransactionFactory;
@@ -113,6 +116,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Field;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -217,6 +221,14 @@ public class TomcatWebAppBuilder impleme
             if (service.getContainer() instanceof Engine) {
                 final Engine engine = (Engine) service.getContainer();
 
+                // add the global router if relevant
+                final URL globalRouterConf = RouterInitializer.serverRouterConfigurationURL();
+                if (globalRouterConf != null) {
+                    final RouterValve routerValve = new RouterValve();
+                    routerValve.setConfigurationPath(globalRouterConf);
+                    engine.getPipeline().addValve(routerValve);
+                }
+
                 parentClassLoader = engine.getParentClassLoader();
 
                 manageCluster(engine.getCluster());

Added: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/Route.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/Route.java?rev=1384795&view=auto
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/Route.java (added)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/Route.java Fri Sep 14 14:18:09 2012
@@ -0,0 +1,94 @@
+/*
+ * 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.tomee.catalina.routing;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Route {
+    private static final String[] EMPTY_CONTEXT = new String[0];
+
+    private final ThreadLocal<Matcher> matcher = new ThreadLocal<Matcher>();
+
+    private Pattern originPattern;
+    private String origin;
+    private String destination;
+
+    public Route from(final String value) {
+        origin = value;
+        originPattern = Pattern.compile(value);
+        return this;
+    }
+
+    public Route to(final String value) {
+        destination = value;
+        return this;
+    }
+
+    public String cleanDestination() {
+        String destination = this.destination;
+
+        final Matcher matcher = this.matcher.get();
+        if (matcher != null) {
+            final String[] context = currentContext();
+            for (int i = 0; i < context.length; i++) {
+                destination = destination.replace("$" + (i + 1), context[i]);
+            }
+        }
+
+        this.matcher.remove(); // single call to this method
+
+        return destination;
+    }
+
+    public String getOrigin() {
+        return origin;
+    }
+
+    public boolean matches(final String uri) {
+        final Matcher matcher = originPattern.matcher(uri);
+        final boolean ok = matcher.matches();
+
+        if (ok) {
+            this.matcher.set(matcher);
+        }
+
+        return ok;
+    }
+
+    private String[] currentContext() {
+        if (matcher.get().groupCount() > 0) {
+            return buildContext(matcher.get());
+        } else {
+            return EMPTY_CONTEXT;
+        }
+    }
+
+    private String[] buildContext(final Matcher matcher) {
+        final Collection<String> values = new ArrayList<String>();
+        for (int i = 1; i < matcher.groupCount() + 1; i++) {
+            values.add(matcher.group(i));
+        }
+        return values.toArray(new String[values.size()]);
+    }
+
+    public String getRawDestination() {
+        return destination;
+    }
+}

Added: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouteFilter.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouteFilter.java?rev=1384795&view=auto
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouteFilter.java (added)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouteFilter.java Fri Sep 14 14:18:09 2012
@@ -0,0 +1,62 @@
+/*
+ * 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.tomee.catalina.routing;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URL;
+
+public final class RouteFilter implements Filter {
+    private SimpleRouter router = new SimpleRouter();
+
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        router.JMXOn(filterConfig.getServletContext().getContextPath());
+    }
+
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
+            chain.doFilter(request, response);
+        }
+
+        final String destination = router.route(((HttpServletRequest) request).getRequestURI());
+        if (destination == null) {
+            chain.doFilter(request, response);
+            return;
+        }
+
+        ((HttpServletResponse) response).sendRedirect(destination);
+    }
+
+    @Override
+    public void destroy() {
+        router.cleanUp();
+    }
+
+    public void initConfigurationPath(final String prefix, final URL configurationPath) {
+        router.setPrefix(prefix);
+        router.readConfiguration(configurationPath);
+    }
+}

Added: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterException.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterException.java?rev=1384795&view=auto
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterException.java (added)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterException.java Fri Sep 14 14:18:09 2012
@@ -0,0 +1,23 @@
+/*
+ * 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.tomee.catalina.routing;
+
+public class RouterException extends RuntimeException {
+    public RouterException(final String s) {
+        super(s);
+    }
+}

Added: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterInitializer.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterInitializer.java?rev=1384795&view=auto
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterInitializer.java (added)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterInitializer.java Fri Sep 14 14:18:09 2012
@@ -0,0 +1,77 @@
+/*
+ * 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.tomee.catalina.routing;
+
+import org.apache.openejb.config.DeploymentLoader;
+import org.apache.openejb.loader.SystemInstance;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Set;
+
+public class RouterInitializer implements ServletContainerInitializer {
+    public static final String ROUTER_CONF = "tomee-router.conf";
+    public static final String WEB_INF = "/WEB-INF/";
+
+    @Override
+    public void onStartup(final Set<Class<?>> classes, final ServletContext servletContext) throws ServletException {
+        final URL routerConfig = configurationURL(servletContext);
+        if (routerConfig != null) {
+            final RouteFilter filter = new RouteFilter();
+            filter.initConfigurationPath(servletContext.getContextPath(), routerConfig);
+            servletContext.addFilter("Routing Filter", filter).addMappingForUrlPatterns(null, false, "/*");
+        }
+    }
+
+    public static URL configurationURL(final ServletContext ctx) {
+        try {
+            return ctx.getResource(WEB_INF + routerConfigurationName());
+        } catch (MalformedURLException e) {
+            // let return null
+        }
+
+        return null;
+    }
+
+    public static String routerConfigurationName() {
+        final String conf = SystemInstance.get().getOptions().get(DeploymentLoader.OPENEJB_ALTDD_PREFIX, (String) null);
+        if (conf == null) {
+            return ROUTER_CONF;
+        } else {
+            return conf + "." + ROUTER_CONF;
+        }
+    }
+
+    public static URL serverRouterConfigurationURL() {
+        final File confDir = SystemInstance.get().getHome().getDirectory();
+        final File configFile = new File(confDir, "conf/" + routerConfigurationName());
+
+        if (configFile.exists()) {
+            try {
+                return configFile.toURI().toURL();
+            } catch (MalformedURLException e) {
+                // let return null
+            }
+        }
+
+        return null;
+    }
+}

Added: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterValve.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterValve.java?rev=1384795&view=auto
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterValve.java (added)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/RouterValve.java Fri Sep 14 14:18:09 2012
@@ -0,0 +1,41 @@
+package org.apache.tomee.catalina.routing;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.net.URL;
+
+public class RouterValve extends ValveBase {
+    private SimpleRouter router = new SimpleRouter();
+
+    @Override
+    public void invoke(final Request request, final Response response) throws IOException, ServletException {
+        final String destination = router.route(request.getRequestURI());
+        if (destination == null) {
+            getNext().invoke(request, response);
+            return;
+        }
+
+        response.sendRedirect(destination);
+    }
+
+    public void setConfigurationPath(URL configurationPath) {
+        router.readConfiguration(configurationPath);
+    }
+
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+        super.startInternal();
+        router.JMXOn("Router Valve " + System.identityHashCode(this));
+    }
+
+    @Override
+    protected synchronized void stopInternal() throws LifecycleException {
+        router.cleanUp();
+        super.stopInternal();
+    }
+}

Added: openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/SimpleRouter.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/SimpleRouter.java?rev=1384795&view=auto
==============================================================================
--- openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/SimpleRouter.java (added)
+++ openejb/trunk/openejb/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/routing/SimpleRouter.java Fri Sep 14 14:18:09 2012
@@ -0,0 +1,210 @@
+/*
+ * 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.tomee.catalina.routing;
+
+import org.apache.openejb.monitoring.DynamicMBeanWrapper;
+import org.apache.openejb.monitoring.LocalMBeanServer;
+import org.apache.openejb.monitoring.ObjectNameBuilder;
+
+import javax.management.ManagedAttribute;
+import javax.management.ManagedOperation;
+import javax.management.ObjectName;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+import javax.servlet.ServletException;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SimpleRouter {
+    private static final Pattern PATTERN = Pattern.compile("(.*)->(.*)");
+
+    private String prefix = "";
+    private ObjectName objectName = null;
+    private Route[] routes = new Route[0];
+    private final Map<String, Route> cache = new ConcurrentHashMap<String, Route>();
+
+    public SimpleRouter readConfiguration(final URL url) {
+        if (url == null) {
+            return this;
+        }
+
+        try {
+            final InputStream is = new BufferedInputStream(url.openStream());
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (!line.isEmpty() && !line.startsWith("#")) {
+                   parseRoute(line);
+                }
+            }
+        } catch (IOException e) {
+            throw new RouterException("can't read " + url.toExternalForm());
+        }
+        return this;
+    }
+
+    private void parseRoute(final String line) {
+        final Matcher matcher = PATTERN.matcher(line);
+        if (matcher.matches()) {
+            final String from = prefix(matcher.group(1).trim());
+            final String to = prefix(matcher.group(2).trim());
+            addRoute(new Route().from(from).to(to));
+        }
+    }
+
+    public String route(final String uri) throws IOException, ServletException {
+        if (uri == null) {
+            return null;
+        }
+
+        final Route cachedRoute = cache.get(uri);
+        if (cachedRoute != null) {
+            cachedRoute.matches(uri);
+            return cachedRoute.cleanDestination();
+        }
+
+        for (Route route : routes) {
+            if (route.matches(uri)) {
+                if (route.getOrigin().equals(uri)) {
+                    cache.put(uri, route);
+                }
+                return route.cleanDestination();
+            }
+        }
+
+        return null;
+    }
+
+    public synchronized void addRoute(final Route route) {
+        final Route[] newRoutes = new Route[routes.length + 1];
+        System.arraycopy(routes, 0, newRoutes, 0, routes.length);
+        newRoutes[routes.length] = route;
+        routes = newRoutes;
+    }
+
+    public void cleanUp() {
+        JMXOff();
+        routes = null;
+        cache.clear();
+    }
+
+    public void setPrefix(final String prefix) {
+        if (prefix == null || prefix.isEmpty() || prefix.equals("/")) {
+            this.prefix = "";
+        } else {
+            this.prefix = prefix;
+        }
+    }
+
+    private String prefix(final String value) {
+        if (prefix != null) {
+            return prefix + value;
+        }
+        return value;
+    }
+
+    public void JMXOn(final String name) {
+        final ObjectNameBuilder jmxName = new ObjectNameBuilder("openejb.management");
+        jmxName.set("J2EEServer", "Router");
+        jmxName.set("J2EEApplication", name);
+        jmxName.set("Type", "SimpleRouter");
+
+        objectName = jmxName.build();
+        try {
+            LocalMBeanServer.get().registerMBean(new DynamicMBeanWrapper(this), objectName);
+        } catch (Exception e) {
+            objectName = null;
+        }
+    }
+
+    public void JMXOff() {
+        if (objectName != null) {
+            try {
+                LocalMBeanServer.get().unregisterMBean(objectName);
+            } catch (Exception e) {
+                // no-op
+            }
+        }
+    }
+
+    @ManagedAttribute
+    public TabularData getActiveRoutes() {
+        if (routes.length == 0) {
+            return null;
+        }
+
+        final OpenType<?>[] types = new OpenType<?>[routes.length];
+        final String[] keys = new String[types.length];
+        final String[] values = new String[types.length];
+
+        for (int i = 0; i < types.length; i++) {
+            types[i] = SimpleType.STRING;
+            keys[i] = routes[i].getOrigin();
+            values[i] = routes[i].getRawDestination();
+        }
+
+        try {
+            final CompositeType ct = new CompositeType("routes", "routes", keys, keys, types);
+            final TabularType type = new TabularType("router", "routes", ct, keys);
+            TabularDataSupport data = new TabularDataSupport(type);
+
+            CompositeData line = new CompositeDataSupport(ct, keys, values);
+            data.put(line);
+            return data;
+        } catch (OpenDataException e) {
+            return null;
+        }
+    }
+
+    @ManagedOperation
+    public void addRoute(final String from, final String to) {
+        addRoute(new Route().from(prefix(from)).to(prefix(to)));
+    }
+
+    @ManagedOperation
+    public void removeRoute(final String from, final String to) {
+        if (routes.length == 0) {
+            return;
+        }
+
+        for (int i = 0; i < routes.length; i++) {
+            if (routes[i].getOrigin().equals(from) && routes[i].getRawDestination().endsWith(to)) {
+                final Route[] newRoutes = new Route[routes.length - 1];
+                System.arraycopy(routes, 0, newRoutes, 0, i);
+                System.arraycopy(routes, i + 1, newRoutes, i, routes.length - i - 1);
+                routes = newRoutes;
+            }
+        }
+    }
+}