/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.osgi.web.wab.extender.internal; import com.liferay.portal.kernel.util.ArrayUtil; import com.liferay.portal.kernel.util.ReflectionUtil; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.osgi.web.servlet.context.helper.ServletContextHelperRegistration; import com.liferay.portal.osgi.web.servlet.context.helper.definition.FilterDefinition; import com.liferay.portal.osgi.web.servlet.context.helper.definition.ListenerDefinition; import com.liferay.portal.osgi.web.servlet.context.helper.definition.ServletDefinition; import com.liferay.portal.osgi.web.servlet.context.helper.definition.WebXMLDefinition; import com.liferay.portal.osgi.web.servlet.jsp.compiler.JspServlet; import com.liferay.portal.osgi.web.wab.extender.internal.adapter.FilterExceptionAdapter; import com.liferay.portal.osgi.web.wab.extender.internal.adapter.ModifiableServletContext; import com.liferay.portal.osgi.web.wab.extender.internal.adapter.ModifiableServletContextAdapter; import com.liferay.portal.osgi.web.wab.extender.internal.adapter.ServletContextListenerExceptionAdapter; import com.liferay.portal.osgi.web.wab.extender.internal.adapter.ServletExceptionAdapter; import com.liferay.portal.osgi.web.wab.extender.internal.registration.FilterRegistrationImpl; import com.liferay.portal.osgi.web.wab.extender.internal.registration.ListenerServiceRegistrationComparator; import com.liferay.portal.osgi.web.wab.extender.internal.registration.ServletRegistrationImpl; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextListener; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.HandlesTypes; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionListener; import org.apache.felix.utils.log.Logger; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; import org.osgi.util.tracker.ServiceTracker; /** * @author Raymond Augé * @author Miguel Pastor */ public class WabBundleProcessor { public WabBundleProcessor(Bundle bundle, Logger logger) { _bundle = bundle; _logger = logger; BundleWiring bundleWiring = _bundle.adapt(BundleWiring.class); _bundleClassLoader = bundleWiring.getClassLoader(); _bundleContext = _bundle.getBundleContext(); } public void destroy() throws Exception { Thread currentThread = Thread.currentThread(); ClassLoader contextClassLoader = currentThread.getContextClassLoader(); try { currentThread.setContextClassLoader(_bundleClassLoader); destroyServlets(); destroyFilters(); destroyListeners(); _bundleContext.ungetService( _servletContextHelperRegistrationServiceReference); } finally { currentThread.setContextClassLoader(contextClassLoader); } } public void init(Dictionary<String, Object> properties) throws Exception { Thread currentThread = Thread.currentThread(); ClassLoader contextClassLoader = currentThread.getContextClassLoader(); try { currentThread.setContextClassLoader(_bundleClassLoader); ServletContextHelperRegistration servletContextHelperRegistration = initContext(); boolean wabShapedBundle = servletContextHelperRegistration.isWabShapedBundle(); if (!wabShapedBundle) { return; } WebXMLDefinition webXMLDefinition = servletContextHelperRegistration.getWebXMLDefinition(); Exception exception = webXMLDefinition.getException(); if (exception != null) { throw exception; } ServletContext servletContext = ModifiableServletContextAdapter.createInstance( servletContextHelperRegistration.getServletContext(), _bundle.getBundleContext(), webXMLDefinition, _logger); initServletContainerInitializers(_bundle, servletContext); ModifiableServletContext modifiableServletContext = (ModifiableServletContext)servletContext; Map<String, String> unregisteredInitParameters = modifiableServletContext.getUnregisteredInitParameters(); if ((unregisteredInitParameters != null) && !unregisteredInitParameters.isEmpty()) { Map<String, Object> attributes = new HashMap<>(); Enumeration<String> attributeNames = servletContext.getAttributeNames(); while (attributeNames.hasMoreElements()) { String attributeName = attributeNames.nextElement(); attributes.put( attributeName, servletContext.getAttribute(attributeName)); } List<ListenerDefinition> listenerDefinitions = modifiableServletContext.getListenerDefinitions(); Map<String, FilterRegistrationImpl> filterRegistrationImpls = modifiableServletContext.getFilterRegistrationImpls(); Map<String, ServletRegistrationImpl> servletRegistrationImpls = modifiableServletContext.getServletRegistrationImpls(); servletContextHelperRegistration.setProperties( unregisteredInitParameters); ServletContext newServletContext = servletContextHelperRegistration.getServletContext(); servletContext = ModifiableServletContextAdapter.createInstance( newServletContext, attributes, listenerDefinitions, filterRegistrationImpls, servletRegistrationImpls, _bundle.getBundleContext(), webXMLDefinition, _logger); modifiableServletContext = (ModifiableServletContext)servletContext; } scanTLDsForListeners(webXMLDefinition, servletContext); initListeners( webXMLDefinition.getListenerDefinitions(), servletContext); initListeners( modifiableServletContext.getListenerDefinitions(), servletContext); modifiableServletContext.registerFilters(); initFilters(webXMLDefinition.getFilterDefinitions()); modifiableServletContext.registerServlets(); initServlets(webXMLDefinition.getServletDefinitions()); } catch (Exception e) { _logger.log( Logger.LOG_ERROR, "Catastrophic initialization failure! Shutting down " + _contextName + " WAB due to: " + e.getMessage(), e); destroy(); throw e; } finally { currentThread.setContextClassLoader(contextClassLoader); } } protected void collectAnnotatedClasses( String classResource, Bundle bundle, Class<?>[] handledTypesArray, Set<Class<?>> annotatedClasses) { String className = classResource.replaceAll("\\.class$", ""); className = className.replaceAll("/", "."); Class<?> annotatedClass = null; try { annotatedClass = bundle.loadClass(className); } catch (Throwable t) { _logger.log(Logger.LOG_DEBUG, t.getMessage()); return; } // Class extends/implements for (Class<?> handledType : handledTypesArray) { if (handledType.isAssignableFrom(annotatedClass) && !Modifier.isAbstract(annotatedClass.getModifiers())) { annotatedClasses.add(annotatedClass); return; } } // Class annotation Annotation[] classAnnotations = new Annotation[0]; try { classAnnotations = annotatedClass.getAnnotations(); } catch (Throwable t) { _logger.log(Logger.LOG_DEBUG, t.getMessage()); } for (Annotation classAnnotation : classAnnotations) { if (ArrayUtil.contains( handledTypesArray, classAnnotation.annotationType())) { annotatedClasses.add(annotatedClass); return; } } // Method annotation Method[] classMethods = new Method[0]; try { classMethods = annotatedClass.getDeclaredMethods(); } catch (Throwable t) { _logger.log(Logger.LOG_DEBUG, t.getMessage()); } for (Method method : classMethods) { Annotation[] methodAnnotations = new Annotation[0]; try { methodAnnotations = method.getDeclaredAnnotations(); } catch (Throwable t) { _logger.log(Logger.LOG_DEBUG, t.getMessage()); } for (Annotation methodAnnotation : methodAnnotations) { if (ArrayUtil.contains( handledTypesArray, methodAnnotation.annotationType())) { annotatedClasses.add(annotatedClass); return; } } } // Field annotation Field[] declaredFields = new Field[0]; try { declaredFields = annotatedClass.getDeclaredFields(); } catch (Throwable t) { _logger.log(Logger.LOG_DEBUG, t.getMessage()); } for (Field field : declaredFields) { Annotation[] fieldAnnotations = new Annotation[0]; try { fieldAnnotations = field.getDeclaredAnnotations(); } catch (Throwable t) { _logger.log(Logger.LOG_DEBUG, t.getMessage()); } for (Annotation fieldAnnotation : fieldAnnotations) { if (ArrayUtil.contains( handledTypesArray, fieldAnnotation.annotationType())) { annotatedClasses.add(annotatedClass); return; } } } } protected void destroyFilters() { for (ServiceRegistration<?> serviceRegistration : _filterServiceRegistrations) { try { serviceRegistration.unregister(); } catch (Exception e) { _logger.log(Logger.LOG_ERROR, e.getMessage(), e); } } _filterServiceRegistrations.clear(); } protected void destroyListeners() { for (ServiceRegistration<?> serviceRegistration : _listenerServiceRegistrations) { try { serviceRegistration.unregister(); } catch (Exception e) { _logger.log(Logger.LOG_ERROR, e.getMessage(), e); } } _listenerServiceRegistrations.clear(); } protected void destroyServlets() { for (ServiceRegistration<?> serviceRegistration : _servletServiceRegistrations) { try { serviceRegistration.unregister(); } catch (Exception e) { _logger.log(Logger.LOG_ERROR, e.getMessage(), e); } } _servletServiceRegistrations.clear(); } protected String[] getClassNames(EventListener eventListener) { List<String> classNamesList = new ArrayList<>(); if (HttpSessionAttributeListener.class.isInstance(eventListener)) { classNamesList.add(HttpSessionAttributeListener.class.getName()); } if (HttpSessionListener.class.isInstance(eventListener)) { classNamesList.add(HttpSessionListener.class.getName()); } if (ServletContextAttributeListener.class.isInstance(eventListener)) { classNamesList.add(ServletContextAttributeListener.class.getName()); } // The following supported listener is omitted on purpose because it is // registered individually. /*if (ServletContextListener.class.isInstance(eventListener)) { classNamesList.add(ServletContextListener.class.getName()); }*/ if (ServletRequestAttributeListener.class.isInstance(eventListener)) { classNamesList.add(ServletRequestAttributeListener.class.getName()); } if (ServletRequestListener.class.isInstance(eventListener)) { classNamesList.add(ServletRequestListener.class.getName()); } return classNamesList.toArray(new String[classNamesList.size()]); } protected ServletContextHelperRegistration initContext() { ServiceTracker <ServletContextHelperRegistration, ServletContextHelperRegistration> serviceTracker = new ServiceTracker<>( _bundleContext, ServletContextHelperRegistration.class, null); serviceTracker.open(); try { ServletContextHelperRegistration servletContextHelperRegistration = serviceTracker.waitForService(2000); WebXMLDefinition webXMLDefinition = servletContextHelperRegistration.getWebXMLDefinition(); _servletContextHelperRegistrationServiceReference = serviceTracker.getServiceReference(); ServletContext servletContext = servletContextHelperRegistration.getServletContext(); _contextName = servletContext.getServletContextName(); servletContext.setAttribute( "jsp.taglib.mappings", webXMLDefinition.getJspTaglibMappings()); servletContext.setAttribute("osgi-bundlecontext", _bundleContext); servletContext.setAttribute("osgi-runtime-vendor", _VENDOR); return servletContextHelperRegistration; } catch (InterruptedException ie) { return ReflectionUtil.throwException(ie); } } protected void initFilters(Map<String, FilterDefinition> filterDefinitions) throws Exception { for (Map.Entry<String, FilterDefinition> entry : filterDefinitions.entrySet()) { FilterDefinition filterDefinition = entry.getValue(); Dictionary<String, Object> properties = new Hashtable<>(); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, _contextName); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_ASYNC_SUPPORTED, filterDefinition.isAsyncSupported()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_DISPATCHER, filterDefinition.getDispatchers()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_NAME, filterDefinition.getName()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN, filterDefinition.getURLPatterns()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_SERVLET, filterDefinition.getServletNames()); properties.put( Constants.SERVICE_RANKING, filterDefinition.getPriority()); Map<String, String> initParameters = filterDefinition.getInitParameters(); for (Entry<String, String> initParametersEntry : initParameters.entrySet()) { String key = initParametersEntry.getKey(); String value = initParametersEntry.getValue(); properties.put( HttpWhiteboardConstants. HTTP_WHITEBOARD_FILTER_INIT_PARAM_PREFIX + key, value); } FilterExceptionAdapter filterExceptionAdaptor = new FilterExceptionAdapter(filterDefinition.getFilter()); ServiceRegistration<Filter> serviceRegistration = _bundleContext.registerService( Filter.class, filterExceptionAdaptor, properties); Exception exception = filterExceptionAdaptor.getException(); if (exception != null) { serviceRegistration.unregister(); throw exception; } _filterServiceRegistrations.add(serviceRegistration); } } protected void initListeners( List<ListenerDefinition> listenerDefinitions, ServletContext servletContext) throws Exception { for (ListenerDefinition listenerDefinition : listenerDefinitions) { Dictionary<String, Object> properties = new Hashtable<>(); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, _contextName); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_LISTENER, Boolean.TRUE.toString()); String[] classNames = getClassNames( listenerDefinition.getEventListener()); if (classNames.length > 0) { ServiceRegistration<?> serviceRegistration = _bundleContext.registerService( classNames, listenerDefinition.getEventListener(), properties); _listenerServiceRegistrations.add(serviceRegistration); } if (!ServletContextListener.class.isInstance( listenerDefinition.getEventListener())) { continue; } ServletContextListenerExceptionAdapter servletContextListenerExceptionAdaptor = new ServletContextListenerExceptionAdapter( (ServletContextListener) listenerDefinition.getEventListener(), servletContext); ServiceRegistration<?> serviceRegistration = _bundleContext.registerService( ServletContextListener.class, servletContextListenerExceptionAdaptor, properties); Exception exception = servletContextListenerExceptionAdaptor.getException(); if (exception != null) { serviceRegistration.unregister(); throw exception; } _listenerServiceRegistrations.add(serviceRegistration); } } protected void initServletContainerInitializers( Bundle bundle, ServletContext servletContext) throws IOException { BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); Enumeration<URL> initializerResources = bundle.getResources( "META-INF/services/javax.servlet.ServletContainerInitializer"); if (initializerResources == null) { return; } while (initializerResources.hasMoreElements()) { URL url = initializerResources.nextElement(); try (InputStream inputStream = url.openStream()) { String fqcn = StringUtil.read(inputStream); processServletContainerInitializerClass( fqcn, bundle, bundleWiring, servletContext); } catch (IOException ioe) { _logger.log(Logger.LOG_ERROR, ioe.getMessage(), ioe); } } } protected void initServlets( Map<String, ServletDefinition> servletDefinitions) throws Exception { for (Entry<String, ServletDefinition> entry : servletDefinitions.entrySet()) { ServletDefinition servletDefinition = entry.getValue(); Dictionary<String, Object> properties = new Hashtable<>(); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, _contextName); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_ASYNC_SUPPORTED, servletDefinition.isAsyncSupported()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_ERROR_PAGE, servletDefinition.getErrorPages()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME, servletDefinition.getName()); String jspFile = servletDefinition.getJspFile(); List<String> urlPatterns = servletDefinition.getURLPatterns(); if (urlPatterns.isEmpty() && (jspFile != null)) { urlPatterns.add(jspFile); } properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, urlPatterns); Map<String, String> initParameters = servletDefinition.getInitParameters(); for (Entry<String, String> initParametersEntry : initParameters.entrySet()) { String key = initParametersEntry.getKey(); String value = initParametersEntry.getValue(); properties.put( HttpWhiteboardConstants. HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + key, value); } ServletExceptionAdapter servletExceptionAdaptor = new ServletExceptionAdapter(servletDefinition.getServlet()); ServiceRegistration<Servlet> serviceRegistration = _bundleContext.registerService( Servlet.class, servletExceptionAdaptor, properties); Exception exception = servletExceptionAdaptor.getException(); if (exception != null) { serviceRegistration.unregister(); throw exception; } _servletServiceRegistrations.add(serviceRegistration); } } protected void processServletContainerInitializerClass( String fqcn, Bundle bundle, BundleWiring bundleWiring, ServletContext servletContext) { Class<? extends ServletContainerInitializer> initializerClass = null; try { Class<?> clazz = bundle.loadClass(fqcn); if (!ServletContainerInitializer.class.isAssignableFrom(clazz)) { return; } initializerClass = clazz.asSubclass( ServletContainerInitializer.class); } catch (Exception e) { _logger.log(Logger.LOG_ERROR, e.getMessage(), e); return; } HandlesTypes handledTypes = initializerClass.getAnnotation( HandlesTypes.class); if (handledTypes == null) { handledTypes = _NULL_HANDLES_TYPES; } Class<?>[] handledTypesArray = handledTypes.value(); if (handledTypesArray == null) { handledTypesArray = new Class<?>[0]; } Collection<String> classResources = bundleWiring.listResources( "/", "*.class", BundleWiring.LISTRESOURCES_RECURSE); if (classResources == null) { classResources = new ArrayList<>(0); } Set<Class<?>> annotatedClasses = new HashSet<>(); for (String classResource : classResources) { URL urlClassResource = bundle.getResource(classResource); if (urlClassResource == null) { continue; } collectAnnotatedClasses( classResource, bundle, handledTypesArray, annotatedClasses); } if (annotatedClasses.isEmpty()) { annotatedClasses = null; } try { ServletContainerInitializer servletContainerInitializer = initializerClass.newInstance(); servletContainerInitializer.onStartup( annotatedClasses, servletContext); } catch (Throwable t) { _logger.log(Logger.LOG_ERROR, t.getMessage(), t); } } protected void scanTLDsForListeners( WebXMLDefinition webXMLDefinition, ServletContext servletContext) { List<String> listenerClassNames = new ArrayList<>(); JspServlet.scanTLDs(_bundle, servletContext, listenerClassNames); for (String listenerClassName : listenerClassNames) { try { Class<?> clazz = _bundle.loadClass(listenerClassName); Class<? extends EventListener> eventListenerClass = clazz.asSubclass(EventListener.class); EventListener eventListener = eventListenerClass.newInstance(); ListenerDefinition listenerDefinition = new ListenerDefinition(); listenerDefinition.setEventListener(eventListener); webXMLDefinition.addListenerDefinition(listenerDefinition); } catch (Exception e) { _logger.log( Logger.LOG_ERROR, "Bundle " + _bundle + " is unable to load listener " + listenerClassName); } } } private static final HandlesTypes _NULL_HANDLES_TYPES = new HandlesTypes() { @Override public Class<? extends Annotation> annotationType() { return null; } @Override public Class<?>[] value() { return new Class<?>[0]; } }; private static final String _VENDOR = "Liferay, Inc."; private final Bundle _bundle; private final ClassLoader _bundleClassLoader; private final BundleContext _bundleContext; private String _contextName; private final Set<ServiceRegistration<Filter>> _filterServiceRegistrations = new ConcurrentSkipListSet<>(); private final Set<ServiceRegistration<?>> _listenerServiceRegistrations = new ConcurrentSkipListSet<>( new ListenerServiceRegistrationComparator()); private final Logger _logger; private ServiceReference<ServletContextHelperRegistration> _servletContextHelperRegistrationServiceReference; private final Set<ServiceRegistration<Servlet>> _servletServiceRegistrations = new ConcurrentSkipListSet<>(); }