001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.test.context.support; 018 019import java.lang.reflect.Method; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022 023import org.springframework.context.ApplicationContext; 024import org.springframework.context.ConfigurableApplicationContext; 025import org.springframework.core.style.ToStringCreator; 026import org.springframework.lang.Nullable; 027import org.springframework.test.annotation.DirtiesContext.HierarchyMode; 028import org.springframework.test.context.CacheAwareContextLoaderDelegate; 029import org.springframework.test.context.MergedContextConfiguration; 030import org.springframework.test.context.TestContext; 031import org.springframework.util.Assert; 032import org.springframework.util.StringUtils; 033 034/** 035 * Default implementation of the {@link TestContext} interface. 036 * 037 * @author Sam Brannen 038 * @author Juergen Hoeller 039 * @author Rob Harrop 040 * @since 4.0 041 */ 042public class DefaultTestContext implements TestContext { 043 044 private static final long serialVersionUID = -5827157174866681233L; 045 046 private final Map<String, Object> attributes = new ConcurrentHashMap<>(4); 047 048 private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; 049 050 private final MergedContextConfiguration mergedContextConfiguration; 051 052 private final Class<?> testClass; 053 054 @Nullable 055 private volatile Object testInstance; 056 057 @Nullable 058 private volatile Method testMethod; 059 060 @Nullable 061 private volatile Throwable testException; 062 063 064 /** 065 * <em>Copy constructor</em> for creating a new {@code DefaultTestContext} 066 * based on the <em>attributes</em> and immutable state of the supplied context. 067 * <p><em>Immutable state</em> includes all arguments supplied to the 068 * {@linkplain #DefaultTestContext(Class, MergedContextConfiguration, 069 * CacheAwareContextLoaderDelegate) standard constructor}. 070 * @throws NullPointerException if the supplied {@code DefaultTestContext} 071 * is {@code null} 072 */ 073 public DefaultTestContext(DefaultTestContext testContext) { 074 this(testContext.testClass, testContext.mergedContextConfiguration, 075 testContext.cacheAwareContextLoaderDelegate); 076 this.attributes.putAll(testContext.attributes); 077 } 078 079 /** 080 * Construct a new {@code DefaultTestContext} from the supplied arguments. 081 * @param testClass the test class for this test context 082 * @param mergedContextConfiguration the merged application context 083 * configuration for this test context 084 * @param cacheAwareContextLoaderDelegate the delegate to use for loading 085 * and closing the application context for this test context 086 */ 087 public DefaultTestContext(Class<?> testClass, MergedContextConfiguration mergedContextConfiguration, 088 CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { 089 090 Assert.notNull(testClass, "Test Class must not be null"); 091 Assert.notNull(mergedContextConfiguration, "MergedContextConfiguration must not be null"); 092 Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null"); 093 this.testClass = testClass; 094 this.mergedContextConfiguration = mergedContextConfiguration; 095 this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; 096 } 097 098 /** 099 * Determine if the {@linkplain ApplicationContext application context} for 100 * this test context is present in the context cache. 101 * @return {@code true} if the application context has already been loaded 102 * and stored in the context cache 103 * @since 5.2 104 * @see #getApplicationContext() 105 * @see CacheAwareContextLoaderDelegate#isContextLoaded 106 */ 107 @Override 108 public boolean hasApplicationContext() { 109 return this.cacheAwareContextLoaderDelegate.isContextLoaded(this.mergedContextConfiguration); 110 } 111 112 /** 113 * Get the {@linkplain ApplicationContext application context} for this 114 * test context. 115 * <p>The default implementation delegates to the {@link CacheAwareContextLoaderDelegate} 116 * that was supplied when this {@code TestContext} was constructed. 117 * @throws IllegalStateException if the context returned by the context 118 * loader delegate is not <em>active</em> (i.e., has been closed) 119 * @see CacheAwareContextLoaderDelegate#loadContext 120 */ 121 @Override 122 public ApplicationContext getApplicationContext() { 123 ApplicationContext context = this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration); 124 if (context instanceof ConfigurableApplicationContext) { 125 @SuppressWarnings("resource") 126 ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context; 127 Assert.state(cac.isActive(), () -> 128 "The ApplicationContext loaded for [" + this.mergedContextConfiguration + 129 "] is not active. This may be due to one of the following reasons: " + 130 "1) the context was closed programmatically by user code; " + 131 "2) the context was closed during parallel test execution either " + 132 "according to @DirtiesContext semantics or due to automatic eviction " + 133 "from the ContextCache due to a maximum cache size policy."); 134 } 135 return context; 136 } 137 138 /** 139 * Mark the {@linkplain ApplicationContext application context} associated 140 * with this test context as <em>dirty</em> (i.e., by removing it from the 141 * context cache and closing it). 142 * <p>The default implementation delegates to the {@link CacheAwareContextLoaderDelegate} 143 * that was supplied when this {@code TestContext} was constructed. 144 * @see CacheAwareContextLoaderDelegate#closeContext 145 */ 146 @Override 147 public void markApplicationContextDirty(@Nullable HierarchyMode hierarchyMode) { 148 this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode); 149 } 150 151 @Override 152 public final Class<?> getTestClass() { 153 return this.testClass; 154 } 155 156 @Override 157 public final Object getTestInstance() { 158 Object testInstance = this.testInstance; 159 Assert.state(testInstance != null, "No test instance"); 160 return testInstance; 161 } 162 163 @Override 164 public final Method getTestMethod() { 165 Method testMethod = this.testMethod; 166 Assert.state(testMethod != null, "No test method"); 167 return testMethod; 168 } 169 170 @Override 171 @Nullable 172 public final Throwable getTestException() { 173 return this.testException; 174 } 175 176 @Override 177 public void updateState(@Nullable Object testInstance, @Nullable Method testMethod, @Nullable Throwable testException) { 178 this.testInstance = testInstance; 179 this.testMethod = testMethod; 180 this.testException = testException; 181 } 182 183 @Override 184 public void setAttribute(String name, @Nullable Object value) { 185 Assert.notNull(name, "Name must not be null"); 186 synchronized (this.attributes) { 187 if (value != null) { 188 this.attributes.put(name, value); 189 } 190 else { 191 this.attributes.remove(name); 192 } 193 } 194 } 195 196 @Override 197 @Nullable 198 public Object getAttribute(String name) { 199 Assert.notNull(name, "Name must not be null"); 200 return this.attributes.get(name); 201 } 202 203 @Override 204 @Nullable 205 public Object removeAttribute(String name) { 206 Assert.notNull(name, "Name must not be null"); 207 return this.attributes.remove(name); 208 } 209 210 @Override 211 public boolean hasAttribute(String name) { 212 Assert.notNull(name, "Name must not be null"); 213 return this.attributes.containsKey(name); 214 } 215 216 @Override 217 public String[] attributeNames() { 218 synchronized (this.attributes) { 219 return StringUtils.toStringArray(this.attributes.keySet()); 220 } 221 } 222 223 224 /** 225 * Provide a String representation of this test context's state. 226 */ 227 @Override 228 public String toString() { 229 return new ToStringCreator(this) 230 .append("testClass", this.testClass) 231 .append("testInstance", this.testInstance) 232 .append("testMethod", this.testMethod) 233 .append("testException", this.testException) 234 .append("mergedContextConfiguration", this.mergedContextConfiguration) 235 .append("attributes", this.attributes) 236 .toString(); 237 } 238 239}