/** * 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.test.rule; import com.liferay.portal.deploy.hot.HookHotDeployListener; import com.liferay.portal.deploy.hot.ServiceWrapperRegistry; import com.liferay.portal.kernel.deploy.hot.DependencyManagementThreadLocal; import com.liferay.portal.kernel.deploy.hot.HotDeployEvent; import com.liferay.portal.kernel.deploy.hot.HotDeployUtil; import com.liferay.portal.kernel.portlet.PortletClassLoaderUtil; import com.liferay.portal.kernel.process.ClassPathUtil; import com.liferay.portal.kernel.servlet.ServletContextPool; import com.liferay.portal.kernel.servlet.filters.invoker.InvokerFilterHelper; import com.liferay.portal.kernel.template.TemplateManagerUtil; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.util.ClassLoaderPool; import com.liferay.portal.kernel.util.InfrastructureUtil; import com.liferay.portal.kernel.util.InitialThreadLocal; import com.liferay.portal.kernel.util.ListUtil; import com.liferay.portal.kernel.util.PortalLifecycleUtil; import com.liferay.portal.kernel.util.PortalUtil; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.ProxyUtil; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.service.test.ServiceTestUtil; import com.liferay.portal.spring.context.PortletContextLoaderListener; import com.liferay.portal.test.mock.AutoDeployMockServletContext; import com.liferay.portal.util.InitUtil; import com.liferay.portal.util.PropsUtil; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.CodeSource; import java.security.ProtectionDomain; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.sql.DataSource; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; import org.springframework.mock.web.MockServletContext; /** * @author Raymond Augé * @author Shuyang Zhou */ public class PACLTestRule implements TestRule { public static final String RESOURCE_PATH = "com/liferay/portal/security/pacl/test/dependencies"; @Override public Statement apply( final Statement statement, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { PortletContextLoaderListener portletContextLoaderListener = new PortletContextLoaderListener(); HotDeployEvent hotDeployEvent = null; if (description.getMethodName() != null) { hotDeployEvent = beforeClass( description, portletContextLoaderListener); } try { invokeStatement(statement, description); } finally { if (hotDeployEvent != null) { afterClass( description, hotDeployEvent, portletContextLoaderListener); } } } }; } public static class PACLTestRuleThreadLocal { public static boolean isDummyDataSourceEnabled() { return _dummyDataSourceEnabled.get(); } public static void setDummyDataSourceEnabled( boolean dummyDataSourceEnabled) { _dummyDataSourceEnabled.set(dummyDataSourceEnabled); } private static final ThreadLocal<Boolean> _dummyDataSourceEnabled = new InitialThreadLocal<>( PACLTestRuleThreadLocal.class + "._dummyDataSourceEnabled", () -> Boolean.FALSE); } protected void afterClass( Description description, HotDeployEvent hotDeployEvent, PortletContextLoaderListener portletContextLoaderListener) throws Exception { if (PACLTestRuleThreadLocal.isDummyDataSourceEnabled()) { LazyConnectionDataSourceProxy lazyConnectionDataSourceProxy = (LazyConnectionDataSourceProxy) InfrastructureUtil.getDataSource(); ReflectionTestUtil.setFieldValue( lazyConnectionDataSourceProxy.getTargetDataSource(), "_dataSource", _originalDataSource); } HotDeployUtil.fireUndeployEvent(hotDeployEvent); ClassLoaderPool.register( hotDeployEvent.getServletContextName(), hotDeployEvent.getContextClassLoader()); PortletClassLoaderUtil.setServletContextName( hotDeployEvent.getServletContextName()); try { portletContextLoaderListener.contextDestroyed( new ServletContextEvent(hotDeployEvent.getServletContext())); } finally { ClassLoaderPool.unregister(hotDeployEvent.getServletContextName()); PortletClassLoaderUtil.setServletContextName(null); } } protected HotDeployEvent beforeClass( Description description, PortletContextLoaderListener portletContextLoaderListener) throws Exception { _testClass = _loadTestClass(description.getTestClass()); _instance = _testClass.newInstance(); ServletContext servletContext = ServletContextPool.get( PortalUtil.getServletContextName()); if (servletContext == null) { servletContext = new AutoDeployMockServletContext( new FileSystemResourceLoader()); servletContext.setAttribute( InvokerFilterHelper.class.getName(), new InvokerFilterHelper()); ServletContextPool.put(PortalUtil.getPathContext(), servletContext); } HotDeployUtil.reset(); HotDeployUtil.registerListener(new HookHotDeployListener()); HotDeployUtil.setCapturePrematureEvents(false); PortalLifecycleUtil.flushInits(); ClassLoader classLoader = _testClass.getClassLoader(); MockServletContext mockServletContext = new MockServletContext( new PACLResourceLoader(classLoader)); mockServletContext.setServletContextName("a-test-hook"); HotDeployEvent hotDeployEvent = getHotDeployEvent( mockServletContext, classLoader); HotDeployUtil.fireDeployEvent(hotDeployEvent); ClassLoaderPool.register( hotDeployEvent.getServletContextName(), hotDeployEvent.getContextClassLoader()); PortletClassLoaderUtil.setServletContextName( hotDeployEvent.getServletContextName()); try { portletContextLoaderListener.contextInitialized( new ServletContextEvent(mockServletContext)); } finally { ClassLoaderPool.unregister(hotDeployEvent.getServletContextName()); PortletClassLoaderUtil.setServletContextName(null); } if (PACLTestRuleThreadLocal.isDummyDataSourceEnabled()) { LazyConnectionDataSourceProxy lazyConnectionDataSourceProxy = (LazyConnectionDataSourceProxy) InfrastructureUtil.getDataSource(); _originalDataSource = ReflectionTestUtil.getAndSetFieldValue( lazyConnectionDataSourceProxy.getTargetDataSource(), "_dataSource", _createDummyDataSource()); } return hotDeployEvent; } protected HotDeployEvent getHotDeployEvent( ServletContext servletContext, ClassLoader classLoader) { boolean dependencyManagementEnabled = DependencyManagementThreadLocal.isEnabled(); try { DependencyManagementThreadLocal.setEnabled(false); return new HotDeployEvent(servletContext, classLoader); } finally { DependencyManagementThreadLocal.setEnabled( dependencyManagementEnabled); } } protected void invokeStatement(Statement statement, Description description) throws Throwable { String methodName = description.getMethodName(); if (methodName == null) { statement.evaluate(); return; } Method method = _testClass.getMethod(description.getMethodName()); method.invoke(_instance); } private static Class<?> _loadTestClass(Class<?> clazz) throws ClassNotFoundException { ProtectionDomain protectionDomain = clazz.getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); ClassLoader classLoader = new PACLClassLoader( new URL[] {codeSource.getLocation()}, clazz.getClassLoader()); return Class.forName(clazz.getName(), true, classLoader); } private DataSource _createDummyDataSource() { Object statment = ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class<?>[] {java.sql.Statement.class}, new InvocationHandler() { @Override public Object invoke( Object proxy, Method method, Object[] args) { String methodName = method.getName(); if (methodName.equals("execute")) { return Boolean.TRUE; } if (methodName.equals("executeUpdate")) { return Integer.MAX_VALUE; } return null; } }); Object preparedStatement = ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class<?>[] {PreparedStatement.class}, new InvocationHandler() { @Override public Object invoke( Object proxy, Method method, Object[] args) { String methodName = method.getName(); if (methodName.equals("execute")) { return Boolean.TRUE; } return null; } }); Object connection = ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class<?>[] {Connection.class}, new InvocationHandler() { @Override public Object invoke( Object proxy, Method method, Object[] args) { String methodName = method.getName(); if (methodName.equals("createStatement")) { return statment; } if (methodName.equals("prepareStatement")) { return preparedStatement; } if (methodName.equals("getAutoCommit")) { return Boolean.TRUE; } return null; } }); return (DataSource)ProxyUtil.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class<?>[] {DataSource.class}, new InvocationHandler() { @Override public Object invoke( Object proxy, Method method, Object[] args) { String methodName = method.getName(); if (methodName.equals("getConnection")) { return connection; } return null; } }); } private static final String _PACKAGE_PATH = "com.liferay.portal.security.pacl.test."; static { ClassPathUtil.initializeClassPaths(new MockServletContext()); List<String> configLocations = ListUtil.fromArray( PropsUtil.getArray(PropsKeys.SPRING_CONFIGS)); InitUtil.initWithSpring(configLocations, true, true); ServiceTestUtil.initMainServletServices(); ServiceTestUtil.initStaticServices(); ServiceTestUtil.initServices(); ServiceTestUtil.initPermissions(); new ServiceWrapperRegistry(); try { Class.forName( TemplateManagerUtil.class.getName(), true, PACLTestRule.class.getClassLoader()); } catch (ClassNotFoundException cnfe) { throw new ExceptionInInitializerError(cnfe); } } private Object _instance; private DataSource _originalDataSource; private Class<?> _testClass; private static class PACLClassLoader extends URLClassLoader { public PACLClassLoader(URL[] urls, ClassLoader parentClassLoader) { super(urls, parentClassLoader); } @Override public URL findResource(String name) { if (_urls.containsKey(name)) { return _urls.get(name); } URL resource = null; if (!name.contains(RESOURCE_PATH)) { String newName = name; if (!newName.startsWith(StringPool.SLASH)) { newName = StringPool.SLASH.concat(newName); } newName = RESOURCE_PATH.concat(newName); resource = super.findResource(newName); } if ((resource == null) && !name.contains(RESOURCE_PATH)) { String newName = name; if (!newName.startsWith(StringPool.SLASH)) { newName = StringPool.SLASH.concat(newName); } newName = RESOURCE_PATH.concat("/WEB-INF/classes").concat( newName); resource = super.findResource(newName); } if (resource == null) { resource = super.findResource(name); } if (resource != null) { _urls.put(name, resource); } return resource; } @Override public URL getResource(String name) { if (name.equals( "com/liferay/util/bean/PortletBeanLocatorUtil.class")) { URL url = findResource("/"); String path = url.getPath(); path = path.substring( 0, path.length() - RESOURCE_PATH.length() - 1); path = path.concat(name); try { return new URL("file", null, path); } catch (MalformedURLException murle) { } } URL url = findResource(name); if (url != null) { return url; } return super.getResource(name); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name.startsWith(_PACKAGE_PATH)) { if (_classes.containsKey(name)) { return _classes.get(name); } Class<?> clazz = super.findClass(name); _classes.put(name, clazz); return clazz; } return super.loadClass(name); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith(_PACKAGE_PATH)) { if (_classes.containsKey(name)) { return _classes.get(name); } Class<?> clazz = super.findClass(name); _classes.put(name, clazz); return clazz; } return super.loadClass(name, resolve); } private final Map<String, Class<?>> _classes = new ConcurrentHashMap<>(); private final Map<String, URL> _urls = new ConcurrentHashMap<>(); } private static class PACLResourceLoader implements ResourceLoader { public PACLResourceLoader(ClassLoader classLoader) { _classLoader = classLoader; } @Override public ClassLoader getClassLoader() { return _classLoader; } @Override public Resource getResource(String location) { ClassLoader classLoader = getClassLoader(); return new ClassPathResource(RESOURCE_PATH + location, classLoader); } private final ClassLoader _classLoader; } }