/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2011-2013 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.enterprise.v3.server;
import com.sun.appserv.server.util.Version;
import com.sun.enterprise.module.ModulesRegistry;
import com.sun.enterprise.module.bootstrap.StartupContext;
import com.sun.enterprise.module.single.StaticModulesRegistry;
import com.sun.enterprise.util.Result;
import org.glassfish.api.FutureProvider;
import org.glassfish.api.StartupRunLevel;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.EventTypes;
import org.glassfish.hk2.api.Context;
import org.glassfish.hk2.api.DynamicConfiguration;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.hk2.api.PreDestroy;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.ServiceLocatorFactory;
import org.glassfish.hk2.runlevel.RunLevel;
import org.glassfish.hk2.runlevel.RunLevelContext;
import org.glassfish.hk2.runlevel.RunLevelController;
import org.glassfish.hk2.runlevel.internal.AsyncRunLevelContext;
import org.glassfish.hk2.runlevel.internal.RunLevelControllerImpl;
import org.glassfish.hk2.utilities.AbstractActiveDescriptor;
import org.glassfish.hk2.utilities.BuilderHelper;
import org.glassfish.hk2.utilities.DescriptorBuilder;
import org.glassfish.internal.api.InitRunLevel;
import org.glassfish.internal.api.PostStartupRunLevel;
import org.glassfish.kernel.event.EventsImpl;
import org.glassfish.main.core.apiexporter.APIExporterImpl;
import org.glassfish.server.ServerEnvironmentImpl;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.jvnet.hk2.annotations.Service;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* AppServerStartup tests.
*
* @author Tom Beerbower
*/
public class AppServerStartupTest {
// ----- data members ----------------------------------------------------
/**
* The AppServerStartup instance to test.
*/
private AppServerStartup as;
/**
* The test results.
*/
private static Results results;
/**
* Map of exceptions to be thrown from the postConstruct.
*/
private static Map<Class, RuntimeException> mapPostConstructExceptions = null;
/**
* List of {@link Future}s returned from {@link FutureProvider#getFutures()} by the Startup
* services during progression to the start up run level.
*/
private static List<TestFuture> listFutures = null;
private ServiceLocator testLocator;
// ----- test initialization ---------------------------------------------
private void initialize(ServiceLocator testLocator) {
DynamicConfigurationService dcs = testLocator.getService(DynamicConfigurationService.class);
DynamicConfiguration config = dcs.createDynamicConfiguration();
config.addActiveDescriptor(BuilderHelper.createConstantDescriptor(new TestSystemTasks()));
// These are services that would normally be started by hk2 core
config.addActiveDescriptor(AppServerStartup.AppInstanceListener.class);
AbstractActiveDescriptor<?> descriptor = BuilderHelper.createConstantDescriptor(new TestModulesRegistry());
descriptor.addContractType(ModulesRegistry.class);
config.addActiveDescriptor(descriptor);
descriptor = BuilderHelper.createConstantDescriptor(new ExecutorServiceFactory().provide());
descriptor.addContractType(ExecutorService.class);
config.addActiveDescriptor(descriptor);
config.addActiveDescriptor(BuilderHelper.createConstantDescriptor(new ServerEnvironmentImpl()));
config.addActiveDescriptor(BuilderHelper.createConstantDescriptor(new EventsImpl()));
config.addActiveDescriptor(BuilderHelper.createConstantDescriptor(new Version()));
config.addActiveDescriptor(BuilderHelper.createConstantDescriptor(new StartupContext()));
config.bind(BuilderHelper.link(RunLevelControllerImpl.class).to(RunLevelController.class).build());
config.addUnbindFilter(BuilderHelper.createContractFilter(RunLevelContext.class.getName()));
config.bind(BuilderHelper.link(RunLevelContext.class).to(Context.class).in(Singleton.class).build());
config.addUnbindFilter(BuilderHelper.createContractFilter(AsyncRunLevelContext.class.getName()));
config.bind(BuilderHelper.link(AsyncRunLevelContext.class).in(Singleton.class).build());
config.bind(BuilderHelper.link(AppServerStartup.class).build());
descriptor = BuilderHelper.createConstantDescriptor(testLocator);
descriptor.addContractType(ServiceLocator.class);
config.addActiveDescriptor(descriptor);
bindService(config, TestInitRunLevelService.class);
bindService(config, TestStartupService.class);
bindService(config, TestStartupRunLevelService.class);
bindService(config, TestPostStartupRunLevelService.class);
bindService(config, CommonClassLoaderServiceImpl.class);
bindService(config, APIClassLoaderServiceImpl.class);
bindService(config, APIExporterImpl.class);
config.commit();
}
private void bindService(DynamicConfiguration configurator, Class<?> service) {
final DescriptorBuilder descriptorBuilder = BuilderHelper.link(service);
final RunLevel rla = service.getAnnotation(RunLevel.class);
if (rla != null) {
descriptorBuilder.to(RunLevel.class).
has(RunLevel.RUNLEVEL_VAL_META_TAG, Collections.singletonList(((Integer) rla.value()).toString())).
has(RunLevel.RUNLEVEL_MODE_META_TAG, Collections.singletonList(((Integer) rla.mode()).toString()));
descriptorBuilder.in(RunLevel.class);
}
Class clazz = service;
while (clazz != null) {
Class<?>[] interfaces = clazz.getInterfaces();
for (int j = 0; j < interfaces.length; j++) {
descriptorBuilder.to(interfaces[j]);
}
clazz = clazz.getSuperclass();
}
final Named named = service.getAnnotation(Named.class);
if (named != null) {
descriptorBuilder.named(named.value());
}
configurator.bind(descriptorBuilder.build());
}
/**
* Reset the results prior to each test.
*/
@Before
public void beforeTest() {
testLocator = ServiceLocatorFactory.getInstance().create("AppServerStartupTest");
initialize(testLocator);
as = testLocator.getService(AppServerStartup.class);
Assert.assertNotNull(as);
mapPostConstructExceptions = new HashMap<Class, RuntimeException>();
listFutures = new LinkedList<TestFuture>();
results = new Results(as.runLevelController);
as.events.register(results);
}
/**
* Ensure that things are stopped after the test... if not then call stop.
*/
@After
public void afterTest() {
if (as != null) {
if (as.runLevelController.getCurrentRunLevel() > 0) {
// force a stop to ensure that the services are released
as.env.setStatus(ServerEnvironment.Status.started);
as.stop();
}
as.events.unregister(results);
}
results = null;
listFutures = null;
mapPostConstructExceptions = null;
ServiceLocatorFactory.getInstance().destroy(testLocator);
testLocator = null;
}
// ----- tests -----------------------------------------------------------
/**
* Call the {@link AppServerStartup#run} method and make sure that
* the run level services are constructed and destroyed at the proper
* run levels.
*/
@Test
public void testRunLevelServices() {
// create the list of Futures returned from TestStartupService
listFutures.add(new TestFuture());
listFutures.add(new TestFuture());
listFutures.add(new TestFuture());
testRunAppServerStartup();
Assert.assertTrue(as.env.getStatus() == ServerEnvironment.Status.started);
Assert.assertEquals(2, results.getListEvents().size());
Assert.assertEquals(EventTypes.SERVER_STARTUP, results.getListEvents().get(0));
Assert.assertEquals(EventTypes.SERVER_READY, results.getListEvents().get(1));
// assert that the run level services have been constructed
Assert.assertTrue(results.isConstructed(TestInitRunLevelService.class, InitRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupRunLevelService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestPostStartupRunLevelService.class, PostStartupRunLevel.VAL));
as.stop();
Assert.assertFalse(as.env.getStatus() == ServerEnvironment.Status.started);
Assert.assertEquals(4, results.getListEvents().size());
Assert.assertEquals(EventTypes.PREPARE_SHUTDOWN, results.getListEvents().get(2));
Assert.assertEquals(EventTypes.SERVER_SHUTDOWN, results.getListEvents().get(3));
// assert that the run level services have been destroyed
Assert.assertTrue(results.isDestroyed(TestPostStartupRunLevelService.class, PostStartupRunLevel.VAL));
Assert.assertTrue(results.isDestroyed(TestStartupService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isDestroyed(TestStartupRunLevelService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isDestroyed(TestInitRunLevelService.class, InitRunLevel.VAL));
}
/**
* Test the {@link AppServerStartup#run} method with an exception thrown from an init
* service that should cause a failure during init. Make sure that the init run level
* services are constructed at the proper run levels.
*/
@Test
public void testRunLevelServicesWithInitException() {
testRunLevelServicesWithException(TestInitRunLevelService.class);
// make sure that the server has not been started
Assert.assertFalse(as.env.getStatus() == ServerEnvironment.Status.started);
// assert that the run level services have been constructed
Assert.assertTrue(results.isConstructed(TestInitRunLevelService.class, InitRunLevel.VAL));
// assert that startup & post-startup services are not constructed since the failure occurs during init
Assert.assertFalse(results.isConstructed(TestStartupService.class));
Assert.assertFalse(results.isConstructed(TestStartupRunLevelService.class));
Assert.assertFalse(results.isConstructed(TestPostStartupRunLevelService.class));
}
/**
* Test the {@link AppServerStartup#run} method with an exception thrown from a startup
* service that should cause a failure during startup. Make sure that the init and
* startup run level services are constructed at the proper run levels.
*/
@Test
public void testRunLevelServicesWithStartupException() {
testRunLevelServicesWithException(TestStartupService.class);
// make sure that the server has not been started
Assert.assertFalse(as.env.getStatus() == ServerEnvironment.Status.started);
Assert.assertTrue(results.getListEvents().contains(EventTypes.SERVER_SHUTDOWN));
// assert that the run level services have been constructed
Assert.assertTrue(results.isConstructed(TestInitRunLevelService.class, InitRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupRunLevelService.class, StartupRunLevel.VAL));
// assert that the post-startup service is not constructed since shutdown occurs during startup
Assert.assertFalse(results.isConstructed(TestPostStartupRunLevelService.class));
}
/**
* Test the {@link AppServerStartup#run} method with an exception thrown from a
* post-startup service that should cause a failure during post-startup. Make sure
* that the run level services are constructed at the proper run levels.
*/
@Test
public void testRunLevelServicesWithPostStartupException() {
testRunLevelServicesWithException(TestPostStartupRunLevelService.class);
// assert that the run level services have been constructed
Assert.assertTrue(results.isConstructed(TestInitRunLevelService.class, InitRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupRunLevelService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestPostStartupRunLevelService.class, PostStartupRunLevel.VAL));
}
/**
* Test the {@link AppServerStartup#run} method with an exception thrown from a
* {@link Future} should cause a failed result during startup. Make sure that the init
* and startup run level services are constructed at the proper run levels. Also ensure
* that the failed {@link Future} causes a shutdown.
*/
@Test
public void testRunLevelServicesWithFuturesException() {
// create the list of Futures returned from TestStartupService
listFutures.add(new TestFuture());
listFutures.add(new TestFuture(new Exception("Exception from Future.")));
listFutures.add(new TestFuture());
testRunAppServerStartup();
// make sure that the server has not been started
Assert.assertFalse(as.env.getStatus() == ServerEnvironment.Status.started);
Assert.assertTrue(results.getListEvents().contains(EventTypes.SERVER_SHUTDOWN));
// assert that the run level services have been constructed
Assert.assertTrue(results.isConstructed(TestInitRunLevelService.class, InitRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupService.class, StartupRunLevel.VAL));
Assert.assertTrue(results.isConstructed(TestStartupRunLevelService.class, StartupRunLevel.VAL));
// assert that the post-startup service is not constructed since shutdown occurs during startup
Assert.assertFalse(results.isConstructed(TestPostStartupRunLevelService.class));
}
// ----- helper methods --------------------------------------------------
/**
* Helper method to run the app server after asserting that the results are clean.
*/
private void testRunAppServerStartup() {
// assert that we have clean results to start
Assert.assertFalse(results.isConstructed(TestInitRunLevelService.class));
Assert.assertFalse(results.isConstructed(TestStartupService.class));
Assert.assertFalse(results.isConstructed(TestStartupRunLevelService.class));
Assert.assertFalse(results.isConstructed(TestPostStartupRunLevelService.class));
as.run();
}
/**
* Helper method to call {@link AppServerStartup#run()}. Sets up an exception
* to be thrown from {@link PostConstruct#postConstruct()} of the given class.
*
* @param badServiceClass the service class that the exception will be thrown from
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
private void testRunLevelServicesWithException(Class badServiceClass) {
// set an exception to be thrown from TestStartupService.postConstruct()
mapPostConstructExceptions.put(badServiceClass,
new RuntimeException("Exception from " + badServiceClass.getSimpleName() + ".postConstruct"));
// create the list of Futures returned from TestStartupService
listFutures.add(new TestFuture());
testRunAppServerStartup();
}
// ----- Results inner class ---------------------------------------------
/**
* Test results
*/
private static class Results implements EventListener {
/**
* Map of constructed run level services to run levels.
*/
private Map<Class, Integer> mapConstructedLevels = new HashMap<Class, Integer>();
/**
* Map of destroyed run level services to run levels.
*/
private Map<Class, Integer> mapDestroyedLevels = new HashMap<Class, Integer>();
/**
* List of server events.
*/
private List<EventTypes> listEvents = new LinkedList<EventTypes>();
/**
* The run level service.
*/
private RunLevelController rls;
public Results(RunLevelController rls) {
this.rls = rls;
}
public void recordConstruction(Class cl) {
mapConstructedLevels.put(cl, rls.getCurrentProceeding().getProposedLevel());
}
public void recordDestruction(Class cl) {
mapDestroyedLevels.put(cl, rls.getCurrentRunLevel() + 1);
}
public boolean isConstructed(Class cl) {
return mapConstructedLevels.keySet().contains(cl);
}
public boolean isConstructed(Class cl, Integer runLevel) {
Integer recLevel = mapConstructedLevels.get(cl);
return recLevel != null && recLevel.equals(runLevel);
}
public boolean isDestroyed(Class cl) {
return mapDestroyedLevels.keySet().contains(cl);
}
public boolean isDestroyed(Class cl, Integer runLevel) {
Integer recLevel = mapDestroyedLevels.get(cl);
return recLevel != null && recLevel.equals(runLevel);
}
public List<EventTypes> getListEvents() {
return listEvents;
}
@Override
public void event(Event event) {
listEvents.add(event.type());
}
}
// ----- test services inner classes -------------------------------------
/**
* Abstract service that will update the test results from
* {@link PostConstruct#postConstruct()}.
*/
public static abstract class TestService implements PostConstruct, PreDestroy {
@Override
public void postConstruct() {
AppServerStartupTest.results.recordConstruction(this.getClass());
if (mapPostConstructExceptions != null) {
RuntimeException ex = mapPostConstructExceptions.get(getClass());
if (ex != null) {
throw ex;
}
}
}
@Override
public void preDestroy() {
AppServerStartupTest.results.recordDestruction(this.getClass());
}
}
/**
* Init service annotated with the new style {@link InitRunLevel} annotation.
*/
@Service
@RunLevel(InitRunLevel.VAL)
public static class TestInitRunLevelService extends TestService {
}
/**
* Startup service that implements the old style Startup interface.
*/
@RunLevel(StartupRunLevel.VAL)
@Service
public static class TestStartupService extends TestService implements FutureProvider {
// Make sure the other one starts first
@SuppressWarnings("unused")
@Inject
private TestStartupRunLevelService dependency;
@Override
public List getFutures() {
return listFutures;
}
}
/**
* Startup service annotated with the new style {@link StartupRunLevel} annotation.
*/
@Service
@RunLevel(StartupRunLevel.VAL)
public static class TestStartupRunLevelService extends TestService {
}
/**
* Post-startup service annotated with the new style {@link PostStartupRunLevel} annotation.
*/
@Service
@RunLevel(PostStartupRunLevel.VAL)
public static class TestPostStartupRunLevelService extends TestService {
}
// ----- TestSystemTasks inner classes -----------------------------------
/**
* Test {@link SystemTasks} implementation.
*/
public static class TestSystemTasks implements SystemTasks {
@Override
public void writePidFile() {
// do nothing.
}
}
// ----- TestModulesRegistry inner classes -------------------------------
/**
* Test {@link ModulesRegistry} implementation.
*/
public static class TestModulesRegistry extends StaticModulesRegistry {
public TestModulesRegistry() {
super(TestModulesRegistry.class.getClassLoader());
}
}
// ----- TestFuture inner classes ----------------------------------------
/**
* Future implementation used for test Startup implementations that
* also implement {@link FutureProvider}.
*/
public static class TestFuture implements Future<Result<Thread>> {
private boolean canceled = false;
private boolean done = false;
private Exception resultException = null;
public TestFuture() {
}
public TestFuture(Exception resultException) {
this.resultException = resultException;
}
@Override
public boolean cancel(boolean b) {
if (done) {
return false;
}
canceled = done = true;
return true;
}
@Override
public boolean isCancelled() {
return canceled;
}
@Override
public boolean isDone() {
return done;
}
@Override
public Result<Thread> get() throws InterruptedException, ExecutionException {
Result<Thread> result = resultException == null ?
new Result<Thread>(Thread.currentThread()) :
new Result<Thread>(resultException);
done = true;
return result;
}
@Override
public Result<Thread> get(long l, TimeUnit timeUnit)
throws InterruptedException, ExecutionException, TimeoutException {
return get();
}
}
}