/* * 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.webservices; import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.Host; import org.apache.catalina.Service; import org.apache.catalina.connector.Connector; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.server.cxf.rs.CxfRsHttpListener; import org.apache.openejb.server.httpd.HttpListener; import org.apache.openejb.server.httpd.util.HttpUtil; import org.apache.openejb.server.rest.RsRegistry; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import org.apache.tomee.catalina.environment.Hosts; import org.apache.tomee.catalina.registration.Registrations; import org.apache.tomee.loader.TomcatHelper; import javax.servlet.DispatcherType; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.TreeMap; public class TomcatRsRegistry implements RsRegistry { private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_STARTUP, TomcatRsRegistry.class); private final Hosts hosts; private List<Connector> connectors; private final Map<Key, HttpListener> listeners = new TreeMap<>(); public TomcatRsRegistry() { for (final Service service : TomcatHelper.getServer().findServices()) { if (service.getContainer() instanceof Engine) { connectors = Arrays.asList(service.findConnectors()); break; } } hosts = SystemInstance.get().getComponent(Hosts.class); } @Override public AddressInfo createRsHttpListener(final String appId, final String webContext, final HttpListener listener, final ClassLoader classLoader, final String completePath, final String virtualHost, final String auth, final String realm) { String path = webContext; if (path == null) { throw new NullPointerException("contextRoot is null"); } if (listener == null) { throw new NullPointerException("listener is null"); } // find the existing host (we do not auto-create hosts) Container host; Context context = null; if (virtualHost == null) { host = hosts.getDefault(); } else { host = hosts.get(virtualHost); } if (host == null) { for (final Host h : hosts) { context = findContext(h, appId, webContext); if (context != null) { host = h; if (classLoader != null && classLoader.equals(context.getLoader().getClassLoader())) { break; } // else try next to find something better } } if (host == null) { throw new IllegalArgumentException("Invalid virtual host '" + virtualHost + "'. Do you have a matching Host entry in the server.xml?"); } } else { context = findContext(host, appId, webContext); } if (context == null) { throw new IllegalStateException("Invalid context '" + webContext + "'. Cannot find context in host " + host.getName()); } final CxfRsHttpListener cxfRsHttpListener = findCxfRsHttpListener(listener); final String description = "tomee-jaxrs-" + listener; String mapping = completePath; if (!completePath.endsWith("/*")) { // respect servlet spec (!= from our embedded listeners) if (completePath.endsWith("*")) { mapping = completePath.substring(0, completePath.length() - 1); } mapping = mapping + "/*"; } final String urlPattern = removeWebContext(webContext, mapping); cxfRsHttpListener.setUrlPattern(urlPattern.substring(0, urlPattern.length() - 1)); final FilterDef filterDef = new FilterDef(); filterDef.setAsyncSupported("true"); filterDef.setDescription(description); filterDef.setFilterName(description); filterDef.setDisplayName(description); filterDef.setFilter(new CXFJAXRSFilter(cxfRsHttpListener, context.findWelcomeFiles())); filterDef.setFilterClass(CXFJAXRSFilter.class.getName()); filterDef.addInitParameter("mapping", urlPattern.substring(0, urlPattern.length() - "/*".length())); // just keep base path context.addFilterDef(filterDef); final FilterMap filterMap = new FilterMap(); filterMap.addURLPattern(urlPattern); for (final DispatcherType type : DispatcherType.values()) { filterMap.setDispatcher(type.name()); } filterMap.setFilterName(filterDef.getFilterName()); context.addFilterMap(filterMap); Registrations.addFilterConfig(context, filterDef); path = address(connectors, host.getName(), webContext); final String key = address(connectors, host.getName(), completePath); listeners.put(new Key(appId, key), listener); return new AddressInfo(path, key); } private CxfRsHttpListener findCxfRsHttpListener(final HttpListener listener) { // can we have some unwrapping to do here? normally no return CxfRsHttpListener.class.cast(listener); } private static String removeWebContext(final String webContext, final String completePath) { if (webContext == null) { return completePath; } return completePath.substring((webContext.length() > 0 && !webContext.startsWith("/") ? 1 : 0) + webContext.length()); } private static Context findContext(final Container host, final String appId, final String webContext) { if (appId != null) { // when using versioning appId is like context#1235 but not the context itself so ensure to test appId first final Context ctx = Context.class.cast(host.findChild('/' + appId)); if (ctx != null) { return ctx; } } Context webapp = Context.class.cast(host.findChild(webContext)); if (webapp == null && "/".equals(webContext)) { // ROOT webapp = Context.class.cast(host.findChild("")); } else if (webapp == null && webContext.length() > 0 && !webContext.startsWith("/")) { webapp = Context.class.cast(host.findChild("/" + webContext)); } return webapp; } private static String address(final Collection<Connector> connectors, final String host, final String path) { final List<String> addresses = new ArrayList<>(); for (final Connector connector : connectors) { final URI address; try { address = new URI(connector.getScheme(), null, host, connector.getPort(), path, null, null); } catch (final Exception e) { // just an URI problem normally...shouldn't occur LOGGER.error("can't add container for path " + path, e); continue; } addresses.add(address.toString()); } return HttpUtil.selectSingleAddress(addresses); } @Override public HttpListener removeListener(final String appId, final String completePath) { if(completePath != null) { String path = completePath; // assure context root with a leading slash if (!path.startsWith("/") && !path.startsWith("http://") && !path.startsWith("https://")) { path = "/" + path; } else { path = completePath; } final Key key = new Key(appId, path); if (listeners.containsKey(key)) { return listeners.remove(key); } } return null; } private static final class Key implements Comparable<Key> { private final String appId; // can be versionned so context is not enough private final String path; private final int hash; // hashmap key so compute only once the hash for perf private Key(final String appId, final String path) { this.appId = appId; this.path = path; this.hash = 31 * (appId != null ? appId.hashCode() : 0) + (path != null ? path.hashCode() : 0); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Key key = Key.class.cast(o); return appId != null ? appId.equals(key.appId) : key.appId == null && (path != null ? path.equals(key.path) : key.path == null); } @Override public int hashCode() { return hash; } @Override public int compareTo(final Key o) { if ((appId != null && !appId.equals(o.appId)) || o.appId != null) { final int appCompare = (appId == null ? "" : appId).compareTo(o.appId == null ? "" : o.appId); if (appCompare != 0) { return appCompare; } } return path.compareTo(o.path); } } }