/** * Copyright (c) 2000-2017 Liferay, Inc. All rights reserved. * * Licensed 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 com.liferay.faces.util.factory.util; import java.io.InputStream; import java.lang.reflect.Method; import com.liferay.faces.util.logging.LoggerTest; import com.liferay.faces.util.product.ProductTest; import junit.framework.Assert; /** * @author Kyle Stiemann */ public final class BlockServiceLoaderUtil extends BlockServiceLoaderUtilCompat { private BlockServiceLoaderUtil() { throw new AssertionError(); } /** * This method loads (or reloads if necessary) the given factory in a separate ClassLoader which disables * ServiceLoader. Since the factories which use ServiceLoader are initialized in a static block, their classes may * need to be reloaded in order to re-run their static initialization. See {@link * ProductTest#loadProductFactoryWhenServicesDirectoryInaccessibleFACES_2966()}, {@link * LoggerTest#loadLoggerFactoryWhenServicesDirectoryInaccessibleFACES_2966()}, {@link * BlockServiceLoaderClassLoader}, and <a href="https://issues.liferay.com/browse/FACES-2966">FACES-2966 Netbeans * auto completion fails for Liferay Faces components</a> for more details. */ public static <T> T getFactoryOutputWithBlockedServiceLoader(Object factoryInput, Class<?> factoryClazz, Class<?> factoryImplClazz) { String factoryClazzSimpleName = factoryClazz.getSimpleName(); InputStream factoryClassFileInputStream = null; InputStream factoryImplClassFileInputStream = null; T factoryOutput = null; ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { ClassLoader classLoader = BlockServiceLoaderUtil.class.getClassLoader(); String factoryClassName = factoryClazz.getName(); BlockServiceLoaderClassLoader blockServiceLoaderClassLoader = new BlockServiceLoaderClassLoader(classLoader, factoryClassName); Thread.currentThread().setContextClassLoader(blockServiceLoaderClassLoader); String factoryClassFileName = factoryClazzSimpleName + ".class"; factoryClassFileInputStream = factoryClazz.getResourceAsStream(factoryClassFileName); // Load or reload the factoryClazz (and any of its innner classes) with blockServiceLoaderClassLoader to // ensure that ServiceLoader cannot be used to initialize the ProductFactory. loadFactoryClassInnerClasses(factoryClassName, factoryClazzSimpleName, factoryClazz, blockServiceLoaderClassLoader); Class<?> factoryClazzWithDisabledServiceLoader = blockServiceLoaderClassLoader.loadClass(factoryClassName, factoryClassFileInputStream); // Load the factory implementation with blockServiceLoaderClassLoader to ensure that it extends the // factoryClazzWithDisabledServiceLoader rather than the factoryClazz (otherwise a ClassCastException will // occur). String factoryImplClassFileName = factoryImplClazz.getSimpleName() + ".class"; factoryImplClassFileInputStream = factoryImplClazz.getResourceAsStream(factoryImplClassFileName); blockServiceLoaderClassLoader.loadClass(factoryImplClazz.getName(), factoryImplClassFileInputStream); String factoryMethodName = "get" + factoryClazzSimpleName.replace("Factory", ""); Method getProductMethod = factoryClazzWithDisabledServiceLoader.getMethod(factoryMethodName, factoryInput.getClass()); @SuppressWarnings("unchecked") T t = (T) getProductMethod.invoke(null, factoryInput); factoryOutput = t; Assert.assertTrue("Invalid test: no class attempted to access service file \"/META-INF/services/" + factoryClassFileName + "\" via the blockServiceLoaderClassLoader. The file may have been accessed by another classloader.", blockServiceLoaderClassLoader.attemptedToAccessServiceFile()); } catch (Throwable t) { throw new AssertionError("Unable to load " + factoryClazzSimpleName + " service when services directory unavailable.", t); } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); close(factoryClassFileInputStream); close(factoryImplClassFileInputStream); } return factoryOutput; } }