/*** * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource * 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 br.com.caelum.vraptor.ioc; import java.net.URL; import java.net.URLClassLoader; import java.util.Collection; import java.util.Collections; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Named; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.hamcrest.Matcher; import org.hamcrest.MatcherAssert; import org.joda.time.LocalDate; import org.joda.time.LocalTime; import org.junit.After; import org.junit.Before; import org.junit.Test; import br.com.caelum.vraptor.ComponentRegistry; import br.com.caelum.vraptor.Converter; import br.com.caelum.vraptor.Result; import br.com.caelum.vraptor.converter.jodatime.LocalDateConverter; import br.com.caelum.vraptor.converter.jodatime.LocalTimeConverter; import br.com.caelum.vraptor.core.BaseComponents; import br.com.caelum.vraptor.core.Converters; import br.com.caelum.vraptor.core.Execution; import br.com.caelum.vraptor.core.MethodInfo; import br.com.caelum.vraptor.core.RequestInfo; import br.com.caelum.vraptor.deserialization.Deserializer; import br.com.caelum.vraptor.deserialization.Deserializers; import br.com.caelum.vraptor.http.route.Route; import br.com.caelum.vraptor.http.route.Router; import br.com.caelum.vraptor.interceptor.InterceptorRegistry; import br.com.caelum.vraptor.ioc.fixture.ComponentFactoryInTheClasspath; import br.com.caelum.vraptor.ioc.fixture.ComponentFactoryInTheClasspath.Provided; import br.com.caelum.vraptor.ioc.fixture.ConverterInTheClasspath; import br.com.caelum.vraptor.ioc.fixture.CustomComponentInTheClasspath; import br.com.caelum.vraptor.ioc.fixture.CustomComponentWithLifecycleInTheClasspath; import br.com.caelum.vraptor.ioc.fixture.DependentOnSomethingFromComponentFactory; import br.com.caelum.vraptor.ioc.fixture.InterceptorInTheClasspath; import br.com.caelum.vraptor.ioc.fixture.ResourceInTheClasspath; import br.com.caelum.vraptor.resource.ResourceMethod; import br.com.caelum.vraptor.scan.ScannotationComponentScannerTest; import com.google.common.base.Objects; import static br.com.caelum.vraptor.VRaptorMatchers.canHandle; import static br.com.caelum.vraptor.VRaptorMatchers.hasOneCopyOf; import static br.com.caelum.vraptor.config.BasicConfiguration.BASE_PACKAGES_PARAMETER_NAME; import static br.com.caelum.vraptor.config.BasicConfiguration.SCANNING_PARAM; import static java.lang.Thread.currentThread; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Acceptance test that checks if the container is capable of giving all * required components. * * @author Guilherme Silveira */ public abstract class GenericContainerTest { protected ContainerProvider provider; protected ServletContext context; protected abstract ContainerProvider getProvider(); protected abstract <T> T executeInsideRequest(WhatToDo<T> execution); protected abstract void configureExpectations(); @Test public void canProvideAllApplicationScopedComponents() { checkAvailabilityFor(true, BaseComponents.getApplicationScoped().keySet()); } @Test public void canProvideAllPrototypeScopedComponents() { checkAvailabilityFor(false, BaseComponents.getPrototypeScoped().keySet()); } @Test public void canProvideAllRequestScopedComponents() { checkAvailabilityFor(false, BaseComponents.getRequestScoped().keySet()); } @ApplicationScoped public static class MyAppComponent { } @Test public void processesCorrectlyAppBasedComponents() { checkAvailabilityFor(true, MyAppComponent.class, MyAppComponent.class); } @Test public void canProvideJodaTimeConverters() { executeInsideRequest(new WhatToDo<String>() { public String execute(RequestInfo request, int counter) { assertNotNull(getFromContainerInCurrentThread(LocalDateConverter.class, request)); assertNotNull(getFromContainerInCurrentThread(LocalTimeConverter.class, request)); Converters converters = getFromContainerInCurrentThread(Converters.class, request); assertTrue(converters.existsFor(LocalDate.class)); assertTrue(converters.existsFor(LocalTime.class)); return null; } }); } @ApplicationScoped public static class MyAppComponentWithLifecycle { private int calls = 0; public int getCalls() { return calls; } @PreDestroy public void z() { calls++; } } @Test public void callsPredestroyExactlyOneTime() throws Exception { MyAppComponentWithLifecycle component = registerAndGetFromContainer(MyAppComponentWithLifecycle.class, MyAppComponentWithLifecycle.class); assertThat(component.calls, is(0)); provider.stop(); assertThat(component.calls, is(1)); provider = getProvider(); start(provider); } @Test public void setsAnAttributeOnRequestWithTheObjectTypeName() throws Exception { executeInsideRequest(new WhatToDo<Void>() { public Void execute(final RequestInfo request, int counter) { return provider.provideForRequest(request, new Execution<Void>() { public Void insideRequest(Container container) { Result result = container.instanceFor(Result.class); HttpServletRequest request = container.instanceFor(HttpServletRequest.class); assertSame(result, Objects.firstNonNull(request.getAttribute("result"), request.getAttribute("defaultResult"))); return null; } }); } }); } @Test public void setsAnAttributeOnSessionWithTheObjectTypeName() throws Exception { registerAndGetFromContainer(MySessionComponent.class, MySessionComponent.class); executeInsideRequest(new WhatToDo<Void>() { public Void execute(final RequestInfo request, int counter) { return provider.provideForRequest(request, new Execution<Void>() { public Void insideRequest(Container container) { HttpSession session = container.instanceFor(HttpSession.class); MySessionComponent component = container.instanceFor(MySessionComponent.class); assertNotNull(component); assertSame(component, session.getAttribute("mySessionComponent")); return null; } }); } }); } @Component @RequestScoped @Named("teste") public static class MyRequestComponent { } @Test public void processesCorrectlyRequestBasedComponents() { checkAvailabilityFor(false, MyRequestComponent.class, MyRequestComponent.class); } @Component @PrototypeScoped public static class MyPrototypeComponent { } @Test public void processesCorrectlyPrototypeBasedComponents() { registerAndGetFromContainer(MyPrototypeComponent.class, MyPrototypeComponent.class); executeInsideRequest(new WhatToDo<Object>() { public Object execute(RequestInfo request, int counter) { return provider.provideForRequest(request, new Execution<Object>() { public Object insideRequest(Container container) { ComponentRegistry registry = container.instanceFor(ComponentRegistry.class); registry.register(MyPrototypeComponent.class, MyPrototypeComponent.class); MyPrototypeComponent instance1 = instanceFor(MyPrototypeComponent.class,container); MyPrototypeComponent instance2 = instanceFor(MyPrototypeComponent.class,container); assertThat(instance1, not(sameInstance(instance2))); return null; } }); } }); } @Test public void supportsComponentFactoriesForCustomInstantiation() { // TODO the registered component is only available in the next request // with Pico. FIX IT! registerAndGetFromContainer(Container.class, TheComponentFactory.class); TheComponentFactory factory = registerAndGetFromContainer(TheComponentFactory.class, null); assertThat(factory, is(notNullValue())); NeedsCustomInstantiation component = registerAndGetFromContainer(NeedsCustomInstantiation.class, null); assertThat(component, is(notNullValue())); registerAndGetFromContainer(DependentOnSomethingFromComponentFactory.class, DependentOnSomethingFromComponentFactory.class); DependentOnSomethingFromComponentFactory dependent = registerAndGetFromContainer( DependentOnSomethingFromComponentFactory.class, null); assertThat(dependent, is(notNullValue())); assertThat(dependent.getDependency(), is(notNullValue())); } @Before public void setup() throws Exception { context = mock(ServletContext.class, "servlet context"); when(context.getMajorVersion()).thenReturn(3); when(context.getInitParameter(BASE_PACKAGES_PARAMETER_NAME)).thenReturn("br.com.caelum.vraptor.ioc.fixture"); when(context.getRealPath("/WEB-INF/classes")).thenReturn(getClassDir()); when(context.getClassLoader()).thenReturn( new URLClassLoader(new URL[] {ScannotationComponentScannerTest.class.getResource("/test-fixture.jar")}, currentThread().getContextClassLoader())); //allowing(context).getInitParameter(ENCODING); //allowing(context).setAttribute(with(any(String.class)), with(any(Object.class))); when(context.getInitParameter(SCANNING_PARAM)).thenReturn("enabled"); configureExpectations(); provider = getProvider(); start(provider); } protected void start(ContainerProvider provider) { provider.start(context); } @After public void tearDown() { provider.stop(); provider = null; } protected <T> void checkAvailabilityFor(final boolean shouldBeTheSame, final Class<T> component, final Class<? super T> componentToRegister) { T firstInstance = registerAndGetFromContainer(component, componentToRegister); T secondInstance = executeInsideRequest(new WhatToDo<T>() { public T execute(RequestInfo request, final int counter) { return provider.provideForRequest(request, new Execution<T>() { public T insideRequest(Container secondContainer) { if (componentToRegister != null && !isAppScoped(secondContainer, componentToRegister)) { ComponentRegistry registry = secondContainer.instanceFor(ComponentRegistry.class); registry.register(componentToRegister, componentToRegister); } ResourceMethod secondMethod = mock(ResourceMethod.class, "rm" + counter); secondContainer.instanceFor(MethodInfo.class).setResourceMethod(secondMethod); return instanceFor(component, secondContainer); } }); } }); checkSimilarity(component, shouldBeTheSame, firstInstance, secondInstance); } protected <T> T registerAndGetFromContainer(final Class<T> componentToBeRetrieved, final Class<?> componentToRegister) { return executeInsideRequest(new WhatToDo<T>() { public T execute(RequestInfo request, final int counter) { return provider.provideForRequest(request, new Execution<T>() { public T insideRequest(Container firstContainer) { if (componentToRegister != null) { ComponentRegistry registry = firstContainer.instanceFor(ComponentRegistry.class); registry.register(componentToRegister, componentToRegister); } ResourceMethod firstMethod = mock(ResourceMethod.class, "rm" + counter); firstContainer.instanceFor(MethodInfo.class).setResourceMethod(firstMethod); return instanceFor(componentToBeRetrieved,firstContainer); } }); } }); } protected <T> T getFromContainer(final Class<T> componentToBeRetrieved) { return executeInsideRequest(new WhatToDo<T>() { public T execute(RequestInfo request, final int counter) { return getFromContainerInCurrentThread(componentToBeRetrieved, request); } }); } protected <T> T getFromContainerInCurrentThread(final Class<T> componentToBeRetrieved, RequestInfo request) { return provider.provideForRequest(request, new Execution<T>() { public T insideRequest(Container firstContainer) { return instanceFor(componentToBeRetrieved,firstContainer); } }); } private boolean isAppScoped(Container secondContainer, Class<?> componentToRegister) { return secondContainer.instanceFor(componentToRegister) != null; } protected void checkSimilarity(Class<?> component, boolean shouldBeTheSame, Object firstInstance, Object secondInstance) { if (shouldBeTheSame) { MatcherAssert.assertThat("Should be the same instance for " + component.getName(), firstInstance, is(equalTo(secondInstance))); } else { MatcherAssert.assertThat("Should not be the same instance for " + component.getName(), firstInstance, is(not(equalTo(secondInstance)))); } } protected void checkAvailabilityFor(boolean shouldBeTheSame, Collection<Class<?>> components) { for (Class<?> component : components) { checkAvailabilityFor(shouldBeTheSame, component, null); } } @Component @RequestScoped static public class DisposableComponent { private boolean destroyed; private final Object dependency = new Object(); public Object getDependency() { return dependency; } @PreDestroy public void preDestroy() { this.destroyed = true; } public boolean isDestroyed() { return destroyed; } } @Component static public class StartableComponent { private boolean started; @PostConstruct public void postConstruct() { this.started = true; } } @Test public void shouldDisposeAfterRequest() { registerAndGetFromContainer(Container.class, DisposableComponent.class); DisposableComponent comp = registerAndGetFromContainer(DisposableComponent.class, null); assertTrue(comp.destroyed); } @Test public void shouldStartBeforeRequestExecution() { registerAndGetFromContainer(Container.class, StartableComponent.class); StartableComponent comp = registerAndGetFromContainer(StartableComponent.class, null); assertTrue(comp.started); } @Test public void canProvideComponentsInTheClasspath() throws Exception { checkAvailabilityFor(false, Collections.<Class<?>> singleton(CustomComponentInTheClasspath.class)); } @Test public void shoudRegisterResourcesInRouter() { Router router = getFromContainer(Router.class); Matcher<Iterable<? super Route>> hasItem = hasItem(canHandle(ResourceInTheClasspath.class, ResourceInTheClasspath.class.getDeclaredMethods()[0])); assertThat(router.allRoutes(), hasItem); } @Test public void shoudUseComponentFactoriesInTheClasspath() { Provided object = getFromContainer(Provided.class); assertThat(object, is(sameInstance(ComponentFactoryInTheClasspath.PROVIDED))); } @Test public void shoudRegisterConvertersInConverters() { executeInsideRequest(new WhatToDo<Converters>() { public Converters execute(RequestInfo request, final int counter) { return provider.provideForRequest(request, new Execution<Converters>() { public Converters insideRequest(Container container) { Converters converters = container.instanceFor(Converters.class); Converter<?> converter = converters.to(Void.class); assertThat(converter, is(instanceOf(ConverterInTheClasspath.class))); return null; } }); } }); } /** * Check if exist {@link Deserializer} registered in VRaptor for determined Content-Types. */ @Test public void shouldReturnAllDefaultDeserializers() { executeInsideRequest(new WhatToDo<Void>(){ public Void execute(RequestInfo request, int counter) { return provider.provideForRequest(request, new Execution<Void>() { public Void insideRequest(Container container) { Deserializers deserializers = container.instanceFor(Deserializers.class); assertNotNull(deserializers.deserializerFor("application/json", container)); assertNotNull(deserializers.deserializerFor("json", container)); assertNotNull(deserializers.deserializerFor("application/xml", container)); assertNotNull(deserializers.deserializerFor("xml", container)); assertNotNull(deserializers.deserializerFor("text/xml", container)); assertNotNull(deserializers.deserializerFor("application/x-www-form-urlencoded", container)); return null; } }); } }); } @Test public void shoudRegisterInterceptorsInInterceptorRegistry() { InterceptorRegistry registry = getFromContainer(InterceptorRegistry.class); assertThat(registry.all(), hasOneCopyOf(InterceptorInTheClasspath.class)); } @Test public void shoudCallPredestroyExactlyOneTimeForComponentsScannedFromTheClasspath() { CustomComponentWithLifecycleInTheClasspath component = getFromContainer(CustomComponentWithLifecycleInTheClasspath.class); assertThat(component.getCallsToPreDestroy(), is(equalTo(0))); provider.stop(); assertThat(component.getCallsToPreDestroy(), is(equalTo(1))); resetProvider(); } @Test public void shoudCallPredestroyExactlyOneTimeForComponentFactoriesScannedFromTheClasspath() { ComponentFactoryInTheClasspath componentFactory = getFromContainer(ComponentFactoryInTheClasspath.class); assertThat(componentFactory.getCallsToPreDestroy(), is(equalTo(0))); provider.stop(); assertThat(componentFactory.getCallsToPreDestroy(), is(equalTo(1))); resetProvider(); } protected void resetProvider() { provider = getProvider(); start(provider); } protected String getClassDir() { return getClass().getResource("/br/com/caelum/vraptor/test").getFile(); } protected <T> T instanceFor(final Class<T> component, Container secondContainer) { return secondContainer.instanceFor(component); } }