/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.grizzly.http.servlet.deployer; import com.sun.grizzly.http.servlet.ServletAdapter; import com.sun.grizzly.http.webxml.schema.*; import com.sun.grizzly.tcp.http11.GrizzlyRequest; import com.sun.grizzly.tcp.http11.GrizzlyResponse; import com.sun.grizzly.tcp.http11.GrizzlyAdapter; import com.sun.grizzly.util.ClassLoaderUtil; import javax.servlet.Servlet; import javax.servlet.Filter; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * {@link com.sun.grizzly.http.webxml.schema.WebApp} Adapter. * * @author Sebastien Dionne * @author Hubert Iwaniuk * @since Aug 25, 2009 */ public class WebAppAdapter extends ServletAdapter { private static Logger logger = Logger.getLogger("WebAppAdapter"); private static final String EMPTY_SERVLET_PATH = ""; private static final String ROOT = "/"; private Map<GrizzlyAdapter, Set<String>> toRegister = new HashMap<GrizzlyAdapter, Set<String>>(); private WebApp webApp; private ClassLoader webAppCL; private String root; private String context; /** * Blank constructor */ public WebAppAdapter(){ } /** * Default constructor, takes care of setting up adapter. * * @param root Root folder, for serving static resources * @param context Context to be deployed to. * @param webApp Web application to be run by this adapter. * @param webAppCL Web application class loader. * @param webdefault Default web application. */ public WebAppAdapter(String root, String context, final WebApp webApp, ClassLoader webAppCL, WebApp webdefault) { this.root = root; this.context = context; if (webdefault != null) { this.webApp = webApp.mergeWith(webdefault); } else { this.webApp = webApp; } this.webAppCL = webAppCL; ClassLoader prevCL = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(webAppCL); try { initialize(); } finally { Thread.currentThread().setContextClassLoader(prevCL); } } private void initialize() { boolean blankContextServletPathFound = false; boolean defaultContextServletPathFound = false; List<String> aliasesUsed = new ArrayList<String>(); for (Map.Entry<ServletAdapter, List<String>> adapterAliases : getServletAdaptersToAlises(webApp, context).entrySet()) { ServletAdapter sa = adapterAliases.getKey(); sa.setClassLoader(webAppCL); // set context params setContextParams(webApp, sa); // set Filters for this context if there are some setFilters(webApp, sa); // set Listeners setListeners(webApp, sa); //set root Folder sa.addRootFolder(root); // create the alias array from the list of urlPattern String alias[] = getAlias(sa, adapterAliases.getValue()); // need to be disabled for JSP sa.setHandleStaticResources(false); // enabled it if not / or /* for (String item : alias) { if (item.endsWith(ROOT) || item.endsWith("/*")) { sa.setHandleStaticResources(true); } } if (logger.isLoggable(Level.FINEST)) { logServletAdapterConfigurationWithAliases(sa, alias); } // keep trace of mapping List<String> stringList = Arrays.asList(alias); aliasesUsed.addAll(stringList); toRegister.put(sa, new HashSet<String>(stringList)); if (ROOT.equals(sa.getServletPath())) { defaultContextServletPathFound = true; } if (EMPTY_SERVLET_PATH.equals(sa.getServletPath())) { blankContextServletPathFound = true; } } // we need one servlet that will handle "/" if (!defaultContextServletPathFound) { logger.log(Level.FINEST, "Adding a ServletAdapter to handle / path"); createAndInstallServletAdapter(root, context, ROOT); } // pour les jsp dans le root du context if (!blankContextServletPathFound && !aliasesUsed.contains(context + ROOT)) { logger.log(Level.FINEST, "Adding a ServletAdapter to handle root path"); createAndInstallServletAdapter(root, context, EMPTY_SERVLET_PATH); } } private void createAndInstallServletAdapter( final String rootFolder, final String context, final String tmpPath) { ServletAdapter sa = new ServletAdapter(); sa.setContextPath(context); sa.setServletPath(tmpPath); sa.setHandleStaticResources(true); sa.addRootFolder(rootFolder); logServletAdapterConfiguration(context, sa); toRegister.put(sa, Collections.singleton(context + ROOT)); } private static void logServletAdapterConfiguration(final String ctx, final ServletAdapter sa) { if (logger.isLoggable(Level.FINEST)) { debugServletAdapterConfiguration(sa, ctx + ROOT); } } public void service(final GrizzlyRequest request, final GrizzlyResponse response) { ClassLoader prevCL = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(webAppCL); try { // TODO here we should take care of welcome-file } finally { Thread.currentThread().setContextClassLoader(prevCL); } } /** * @param webApp Contains the info about the web.xml * @param context context of the application * @return a list of ServletAdapter with the UrlPattern for each Servlet. */ public Map<ServletAdapter, List<String>> getServletAdaptersToAlises(WebApp webApp, String context) { Map<ServletAdapter, List<String>> servletAdapterMap; if (webApp == null || webApp.getServletMapping() == null || webApp.getServletMapping().isEmpty()) { ServletAdapter sa = new ServletAdapter(); sa.setContextPath(context); sa.setServletPath(EMPTY_SERVLET_PATH); servletAdapterMap = new HashMap<ServletAdapter, List<String>>(1); servletAdapterMap.put(sa, Arrays.asList(ROOT)); } else { // if we have servletMapping final List<ServletMapping> mappings = webApp.getServletMapping(); servletAdapterMap = new HashMap<ServletAdapter, List<String>>(mappings.size()); for (ServletMapping servletMapping : mappings) { List<String> urlPatternList = servletMapping.getUrlPattern(); //TODO support multiple urlPattern ??? for that the SA need to have multiple servletpath ? // ex : urlPattern = /xxx/1.y // urlPattern = /xxx/yyy/1.z // the servlet path will not be the same.. do we create 2 SA ? // WE WILL GET ONLY THE FIRST urlPattern String urlPattern; //if empty, assume "/" as context if (urlPatternList == null || urlPatternList.isEmpty()) { urlPattern = ROOT; } else { urlPattern = urlPatternList.get(0); } // get the context path. The urlPattern could be "", "/", // "/bla/xyz/..." // just to be sure we don't obtain two slash "//" if (!urlPattern.startsWith(ROOT)) { urlPattern = String.format("%s%s", ROOT, urlPattern); } String servletUrlPattern = urlPattern; if (servletUrlPattern.indexOf("//") > -1) { servletUrlPattern = servletUrlPattern.replaceAll("//", ROOT); } ServletAdapter sa = createServletAdapter(context, servletUrlPattern); // Set the Servlet setServlet(webApp, sa, servletMapping); servletAdapterMap.put(sa, Arrays.asList(urlPattern)); } } return servletAdapterMap; } protected static void setServlet(WebApp webApp, ServletAdapter sa, ServletMapping servletMapping) { //we need to get the servlet according to the servletMapping for (com.sun.grizzly.http.webxml.schema.Servlet servletItem : webApp.getServlet()) { if (servletItem.getServletName().equalsIgnoreCase(servletMapping.getServletName())) { sa.setServletInstance((Servlet) ClassLoaderUtil.load(servletItem.getServletClass())); List<InitParam> initParamsList = servletItem.getInitParam(); if (initParamsList != null && !initParamsList.isEmpty()) { for (InitParam element : initParamsList) { sa.addInitParameter(element.getParamName(), element.getParamValue()); } } // load-on-startup int loadOnStartup = -1; try { loadOnStartup = Integer.parseInt(servletItem.loadOnStartup); } catch (Exception e){ } if(loadOnStartup!=-1){ sa.setProperty(ServletAdapter.LOAD_ON_STARTUP, Boolean.TRUE); } break; } } } /** * @param path Path to convert. * @return Converted path. */ public static String getServletPath(String path) { String result; if (path == null) { result = ROOT; } else { // need to replace "\" and "\\" by "/" result = path.replaceAll("\\\\", ROOT); // the path need to start by "/" if (!result.startsWith(ROOT)) { result = ROOT + result; } // we could have multiples options // /servlet // /servlet/ // /servlet/subpath // /servlet/subpath/ // /servlet/* or /servlet/*.x // all theses must return /servlet // remove the trailing "/" if (result.endsWith(ROOT) && result.length() > 1) { result = result.substring(0, result.length() - 1); } else if (!result.endsWith(ROOT)) { // find the last "/" int index = result.lastIndexOf(ROOT); // find if we have a wildcard "*" or a extension "." if (result.lastIndexOf('*') > index || result.lastIndexOf('.') > index) { // do we have something like : /a.cdcdcd or /* or /*.abc if (index == 0) { result = ROOT; } else if (index > 0) { //remove the urlpattern if (index < result.length()) { result = result.substring(0, index); } } } } } return result; } protected ServletAdapter createServletAdapter( final String context, final String servletUrlPattern) { ServletAdapter sa = new ServletAdapter(); sa.setContextPath(context); // be sure not the get the extension mapping // like /blabla/*.jsp sa.setServletPath(getServletPath(servletUrlPattern)); return sa; } protected static void setFilters(WebApp webApp, ServletAdapter sa) { if (webApp == null || sa == null) { return; } // Add the Filters List<com.sun.grizzly.http.webxml.schema.Filter> filterList = webApp.getFilter(); List<FilterMapping> filterMappingList = webApp.getFilterMapping(); if (filterList != null && !filterList.isEmpty()) { for (com.sun.grizzly.http.webxml.schema.Filter filterItem : filterList) { // we had the filter if the url-pattern is for this context // we need to get the right filter-mapping form the name for (FilterMapping filterMapping : filterMappingList) { //we need to find in the filterMapping is for this filter if (filterItem.getFilterName().equalsIgnoreCase(filterMapping.getFilterName())) { Filter filter = (Filter) ClassLoaderUtil.load(filterItem.getFilterClass()); // initParams List<InitParam> initParamList = filterItem.getInitParam(); Map<String, String> initParamsMap = new HashMap<String, String>(); if (initParamList != null) { for (InitParam param : initParamList) { initParamsMap.put(param.getParamName(), param.getParamValue()); } } sa.addFilter(filter, filterItem.getFilterName(), initParamsMap); } } } } } protected static void setListeners(WebApp webApp, ServletAdapter sa) { if (webApp == null || sa == null) { return; } // Add the Listener List<Listener> listeners = webApp.getListener(); if (listeners != null) { for (Listener element : listeners) { sa.addServletListener(element.getListenerClass()); } } } protected static void setContextParams(WebApp webApp, ServletAdapter sa) { if(webApp == null || sa == null) { return; } // Add the context param List<ContextParam> contextParmas = webApp.getContextParam(); if(contextParmas != null) { for(ContextParam element : contextParmas) { sa.addContextParameter(element.getParamName(), element.getParamValue()); } } } /** * @param sa ServletAdapter * @param aliases contains the list of UrlPattern for this ServletAdapter * @return the alias list for this ServletAdapter */ public static String[] getAlias(ServletAdapter sa, Collection<String> aliases) { List<String> aliasList; if (sa == null || aliases == null) { // Default context aliasList = Arrays.asList(ROOT); } else { aliasList = new ArrayList<String>(aliases.size()); for (String urlPattern : aliases) { String mapping = EMPTY_SERVLET_PATH; if (!sa.getServletPath().equals(urlPattern) && urlPattern.indexOf(sa.getServletPath()) > -1) { mapping = urlPattern.substring(urlPattern.indexOf(sa.getServletPath()) + sa.getServletPath().length()); } // the alias is the context + servletPath + mapping String aliasTmp = String.format("%s%s%s", sa.getContextPath(), sa.getServletPath(), mapping); if (aliasTmp.indexOf("//") > -1) { aliasTmp = aliasTmp.replaceAll("//", ROOT); } aliasList.add(aliasTmp); } } return aliasList.toArray(new String[aliasList.size()]); } static void logServletAdapterConfigurationWithAliases( final ServletAdapter sa, final String[] aliases) { StringBuilder sb = new StringBuilder(64); sb.append('['); for (String item : aliases) { sb.append(item).append(','); } sb.deleteCharAt(sb.length() - 1); sb.append(']'); debugServletAdapterConfiguration(sa, sb.toString()); } static void debugServletAdapterConfiguration( final ServletAdapter sa, final String alias) { logger.log(Level.FINEST, "sa context=" + sa.getContextPath()); logger.log(Level.FINEST, "sa servletPath=" + sa.getServletPath()); logger.log(Level.FINEST, "sa alias=" + alias); logger.log(Level.FINEST, "sa rootFolders=" + sa.getRootFolders()); } public Map<GrizzlyAdapter, Set<String>> getToRegister() { return toRegister; } }