/** * * 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.Wrapper; import org.apache.catalina.authenticator.BasicAuthenticator; import org.apache.catalina.authenticator.DigestAuthenticator; import org.apache.catalina.authenticator.NonLoginAuthenticator; import org.apache.catalina.authenticator.SSLAuthenticator; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardServer; import org.apache.catalina.core.StandardWrapper; import org.apache.openejb.assembler.classic.ServletInfo; import org.apache.openejb.assembler.classic.WebAppBuilder; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.server.httpd.HttpListener; import org.apache.openejb.server.webservices.WsRegistry; import org.apache.openejb.server.webservices.WsServlet; import org.apache.tomcat.util.descriptor.web.LoginConfig; import org.apache.tomcat.util.descriptor.web.SecurityCollection; import org.apache.tomcat.util.descriptor.web.SecurityConstraint; import org.apache.tomee.catalina.IgnoredStandardContext; import org.apache.tomee.catalina.OpenEJBValve; import org.apache.tomee.catalina.TomEERuntimeException; import org.apache.tomee.catalina.TomcatWebAppBuilder; import org.apache.tomee.loader.TomcatHelper; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static java.util.Arrays.asList; public class TomcatWsRegistry implements WsRegistry { private static final String WEBSERVICE_SUB_CONTEXT = forceSlash(SystemInstance.get().getOptions().get("tomee.jaxws.subcontext", "/webservices")); private static final boolean WEBSERVICE_OLDCONTEXT_ACTIVE = SystemInstance.get().getOptions().get("tomee.jaxws.oldsubcontext", false); private static final String TOMEE_JAXWS_SECURITY_ROLE_PREFIX = "tomee.jaxws.security-role."; private final Map<Key, Context> webserviceContexts = new ConcurrentHashMap<>(); private final Map<String, Integer> fakeContextReferences = new ConcurrentHashMap<>(); private Engine engine; private List<Connector> connectors; public TomcatWsRegistry() { final StandardServer standardServer = TomcatHelper.getServer(); for (final Service service : standardServer.findServices()) { if (service.getContainer() instanceof Engine) { connectors = Arrays.asList(service.findConnectors()); engine = (Engine) service.getContainer(); break; } } } private static String forceSlash(final String property) { if (property == null) { return "/"; } if (!property.startsWith("/")) { return "/" + property; } return property; } @Override public List<String> setWsContainer(final HttpListener httpListener, final ClassLoader classLoader, String contextRoot, String virtualHost, final ServletInfo servletInfo, final String realmName, final String transportGuarantee, final String authMethod, final String moduleId) throws Exception { if (virtualHost == null) { virtualHost = engine.getDefaultHost(); } final Container host = engine.findChild(virtualHost); if (host == null) { throw new IllegalArgumentException("Invalid virtual host '" + virtualHost + "'. Do you have a matchiing Host entry in the server.xml?"); } if ("ROOT".equals(contextRoot)) { // doesn't happen in tomee itself but with all our tooling around contextRoot = ""; } if (!contextRoot.startsWith("/") && !contextRoot.isEmpty()) { contextRoot = "/" + contextRoot; } final Context context = (Context) host.findChild(contextRoot); if (context == null) { throw new IllegalArgumentException("Could not find web application context " + contextRoot + " in host " + host.getName()); } final Wrapper wrapper = (Wrapper) context.findChild(servletInfo.servletName); if (wrapper == null) { throw new IllegalArgumentException("Could not find servlet " + servletInfo.servletName + " in web application context " + context.getName()); } // for Pojo web services, we need to change the servlet class which is the service implementation // by the WsServler class wrapper.setServletClass(WsServlet.class.getName()); if (wrapper.getServlet() != null) { wrapper.unload(); // deallocate previous one wrapper.load(); // reload this one withuot unloading it to keep the instance - unload is called during stop() // boolean controlling this method call can't be set to false through API so let do it ourself wrapper.getServlet().init(StandardWrapper.class.cast(wrapper)); // or Reflections.set(wrapper, "instanceInitialized", false); } setWsContainer(context, wrapper, httpListener); // add service locations final List<String> addresses = new ArrayList<>(); for (final Connector connector : connectors) { for (final String mapping : wrapper.findMappings()) { final URI address = new URI(connector.getScheme(), null, host.getName(), connector.getPort(), (contextRoot.startsWith("/") ? "" : "/") + contextRoot + mapping, null, null); addresses.add(address.toString()); } } return addresses; } @Override public void clearWsContainer(final String contextRoot, String virtualHost, final ServletInfo servletInfo, final String moduleId) { if (virtualHost == null) { virtualHost = engine.getDefaultHost(); } final Container host = engine.findChild(virtualHost); if (host == null) { throw new IllegalArgumentException("Invalid virtual host '" + virtualHost + "'. Do you have a matchiing Host entry in the server.xml?"); } final Context context = (Context) host.findChild("/" + contextRoot); if (context == null) { throw new IllegalArgumentException("Could not find web application context " + contextRoot + " in host " + host.getName()); } final Wrapper wrapper = (Wrapper) context.findChild(servletInfo.servletName); if (wrapper == null) { throw new IllegalArgumentException("Could not find servlet " + servletInfo.servletName + " in web application context " + context.getName()); } // clear the webservice ref in the servlet context final String webServicecontainerId = wrapper.findInitParameter(WsServlet.WEBSERVICE_CONTAINER); if (webServicecontainerId != null) { context.getServletContext().removeAttribute(webServicecontainerId); wrapper.removeInitParameter(WsServlet.WEBSERVICE_CONTAINER); } } // String webContext, String path, HttpListener httpListener, String virtualHost, String realmName, String transportGuarantee, String authMethod, ClassLoader classLoader @Override public List<String> addWsContainer(final HttpListener httpListener, final ClassLoader classLoader, final String context, String virtualHost, String path, final String realmName, final String transportGuarantee, final String authMethod, final String moduleId) throws Exception { if (path == null) { throw new NullPointerException("contextRoot is null"); } if (httpListener == null) { throw new NullPointerException("httpListener is null"); } // assure context root with a leading slash if (!path.startsWith("/")) { path = "/" + path; } // find the existing host (we do not auto-create hosts) if (virtualHost == null) { virtualHost = engine.getDefaultHost(); } final Container host = engine.findChild(virtualHost); if (host == null) { throw new IllegalArgumentException("Invalid virtual host '" + virtualHost + "'. Do you have a matchiing Host entry in the server.xml?"); } final List<String> addresses = new ArrayList<>(); // build contexts // - old way (/*) if (WEBSERVICE_OLDCONTEXT_ACTIVE) { deployInFakeWebapp(path, classLoader, authMethod, transportGuarantee, realmName, host, httpListener, addresses, context); } // - new way (/<webappcontext>/webservices/<name>) if webcontext is specified if (context != null) { final Context webAppContext = findContext(context, moduleId, host); if (webAppContext != null) { // sub context = '/' means the service address is provided by webservices if (WEBSERVICE_SUB_CONTEXT.equals("/") && path.startsWith("/")) { addServlet(host, webAppContext, path, httpListener, path, addresses, false, moduleId); } else if (WEBSERVICE_SUB_CONTEXT.equals("/") && !path.startsWith("/")) { addServlet(host, webAppContext, '/' + path, httpListener, path, addresses, false, moduleId); } else { addServlet(host, webAppContext, WEBSERVICE_SUB_CONTEXT + path, httpListener, path, addresses, false, moduleId); } } else if (!WEBSERVICE_OLDCONTEXT_ACTIVE) { // deploying in a jar deployInFakeWebapp(path, classLoader, authMethod, transportGuarantee, realmName, host, httpListener, addresses, context); } } return addresses; } private Context findContext(final String context, final String moduleId, final Container host) { String root = context; if ("ROOT".equals(root)) { root = ""; } if (!root.startsWith("/") && !root.isEmpty()) { root = '/' + root; } Context webAppContext = null; for (final String name : asList(moduleId == null ? null : "/" + moduleId, root)) { if (name == null) { continue; } webAppContext = Context.class.cast(host.findChild(name)); if (webAppContext != null) { break; } } return webAppContext; } private void deployInFakeWebapp(final String path, final ClassLoader classLoader, final String authMethod, final String transportGuarantee, final String realmName, final Container host, final HttpListener httpListener, final List<String> addresses, final String name) { Container context = host.findChild(name); if (context == null) { context = createNewContext(classLoader, authMethod, transportGuarantee, realmName, name); host.addChild(context); } final Integer ref = fakeContextReferences.get(name); if (ref == null) { fakeContextReferences.put(name, 0); } else { fakeContextReferences.put(name, ref + 1); } String mapping = path; if (!mapping.startsWith("/")) { // TODO: check it can happen or move it away mapping = '/' + mapping; } addServlet(host, (Context) context, mapping, httpListener, path, addresses, true, null); } private static Context createNewContext(final ClassLoader classLoader, String authMethod, String transportGuarantee, final String realmName, final String name) { String path = name; if (path == null) { path = "/"; } if (!path.startsWith("/")) { path = "/" + path; } final StandardContext context = new IgnoredStandardContext(); context.setPath(path); context.setDocBase(""); context.setParentClassLoader(classLoader); context.setDelegate(true); context.setName(name); ((TomcatWebAppBuilder) SystemInstance.get().getComponent(WebAppBuilder.class)).initJ2EEInfo(context); // Configure security if (authMethod != null) { authMethod = authMethod.toUpperCase(); } if (transportGuarantee != null) { transportGuarantee = transportGuarantee.toUpperCase(); } if (authMethod == null || "NONE".equals(authMethod)) { //NOPMD // ignore none for now as the NonLoginAuthenticator seems to be completely hosed } else if ("BASIC".equals(authMethod) || "DIGEST".equals(authMethod) || "CLIENT-CERT".equals(authMethod)) { //Setup a login configuration final LoginConfig loginConfig = new LoginConfig(); loginConfig.setAuthMethod(authMethod); loginConfig.setRealmName(realmName); context.setLoginConfig(loginConfig); //Setup a default Security Constraint final String securityRole = SystemInstance.get().getProperty(TOMEE_JAXWS_SECURITY_ROLE_PREFIX + name, "default"); for (final String role : securityRole.split(",")) { final SecurityCollection collection = new SecurityCollection(); collection.addMethod("GET"); collection.addMethod("POST"); collection.addPattern("/*"); collection.setName(role); final SecurityConstraint sc = new SecurityConstraint(); sc.addAuthRole("*"); sc.addCollection(collection); sc.setAuthConstraint(true); sc.setUserConstraint(transportGuarantee); context.addConstraint(sc); context.addSecurityRole(role); } //Set the proper authenticator if ("BASIC".equals(authMethod)) { context.addValve(new BasicAuthenticator()); } else if ("DIGEST".equals(authMethod)) { context.addValve(new DigestAuthenticator()); } else if ("CLIENT-CERT".equals(authMethod)) { context.addValve(new SSLAuthenticator()); } else if ("NONE".equals(authMethod)) { context.addValve(new NonLoginAuthenticator()); } context.getPipeline().addValve(new OpenEJBValve()); } else { throw new IllegalArgumentException("Invalid authMethod: " + authMethod); } return context; } private void addServlet(final Container host, final Context context, final String mapping, final HttpListener httpListener, final String path, final List<String> addresses, final boolean fakeDeployment, final String moduleId) { // build the servlet final Wrapper wrapper = context.createWrapper(); wrapper.setName("webservice" + path.substring(1)); wrapper.setServletClass(WsServlet.class.getName()); // add servlet to context context.addChild(wrapper); context.addServletMappingDecoded(mapping, wrapper.getName()); final String webServicecontainerID = wrapper.getName() + WsServlet.WEBSERVICE_CONTAINER + httpListener.hashCode(); wrapper.addInitParameter(WsServlet.WEBSERVICE_CONTAINER, webServicecontainerID); setWsContainer(context, wrapper, httpListener); webserviceContexts.put(new Key(path, moduleId), context); // register wsdl locations for service-ref resolution for (final Connector connector : connectors) { final StringBuilder fullContextpath; if (!WEBSERVICE_OLDCONTEXT_ACTIVE && !fakeDeployment) { String contextPath = context.getPath(); if (contextPath == null || !contextPath.isEmpty()) { if (contextPath != null && !contextPath.startsWith("/")) { contextPath = "/" + contextPath; } else if (contextPath == null) { contextPath = "/"; } } fullContextpath = new StringBuilder(contextPath); if (!WEBSERVICE_SUB_CONTEXT.equals("/")) { fullContextpath.append(WEBSERVICE_SUB_CONTEXT); } fullContextpath.append(path); } else { fullContextpath = new StringBuilder(context.getPath()).append(path); } try { final URI address = new URI(connector.getScheme(), null, host.getName(), connector.getPort(), fullContextpath.toString(), null, null); addresses.add(address.toString()); } catch (final URISyntaxException ignored) { // no-op } } } public void removeWsContainer(String path, final String moduleId) { if (path == null) { return; } // assure context root with a leading slash if (!path.startsWith("/")) { path = "/" + path; } if (TomcatHelper.isStopping()) { return; } Context context = webserviceContexts.remove(new Key(path, moduleId)); if (context == null) { // fake context = webserviceContexts.remove(new Key(path, null)); } Integer refs = 1; // > 0 to avoid to destroy the context if not mandatory if (context != null) { final String name = context.getName(); refs = fakeContextReferences.remove(name); if (refs != null && refs > 0) { fakeContextReferences.put(name, refs - 1); } } if ((WEBSERVICE_OLDCONTEXT_ACTIVE || (refs != null && refs == 0)) && context != null) { try { context.stop(); context.destroy(); } catch (final Exception e) { throw new TomEERuntimeException(e); } final Host host = (Host) context.getParent(); host.removeChild(context); } // else let tomcat manages its context } private void setWsContainer(final Context context, final Wrapper wrapper, final HttpListener wsContainer) { // Make up an ID for the WebServiceContainer // put a reference the ID in the init-params // put the WebServiceContainer in the webapp context keyed by its ID final String webServicecontainerID = wrapper.getName() + WsServlet.WEBSERVICE_CONTAINER + wsContainer.hashCode(); context.getServletContext().setAttribute(webServicecontainerID, wsContainer); wrapper.addInitParameter(WsServlet.WEBSERVICE_CONTAINER, webServicecontainerID); } private static class Key { private final String moduleId; private final String path; private final int hash; public Key(final String path, final String moduleId) { this.moduleId = moduleId; this.path = path; this.hash = 31 * (moduleId != null ? moduleId.hashCode() : 0) + path.hashCode(); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || Key.class != o.getClass()) { return false; } final Key key = Key.class.cast(o); return !(moduleId != null ? !moduleId.equals(key.moduleId) : key.moduleId != null) && path.equals(key.path); } @Override public int hashCode() { return hash; } @Override public String toString() { return "Key{" + "moduleId='" + moduleId + '\'' + ", path='" + path + '\'' + '}'; } } }