001/** 002 * Copyright (C) 2006-2023 Talend Inc. - www.talend.com 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 * http://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 */ 016package org.talend.sdk.component.junit5; 017 018import static java.util.Optional.ofNullable; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.AnnotatedElement; 022import java.util.Optional; 023 024import org.junit.jupiter.api.extension.AfterAllCallback; 025import org.junit.jupiter.api.extension.AfterEachCallback; 026import org.junit.jupiter.api.extension.BeforeAllCallback; 027import org.junit.jupiter.api.extension.BeforeEachCallback; 028import org.junit.jupiter.api.extension.ExtensionContext; 029import org.junit.platform.commons.util.AnnotationUtils; 030import org.talend.sdk.component.junit.BaseComponentsHandler; 031import org.talend.sdk.component.junit.base.junit5.JUnit5InjectionSupport; 032import org.talend.sdk.component.junit.environment.Environment; 033 034/** 035 * Extension allowing the test to use a {@link org.talend.sdk.component.junit.ComponentsHandler} 036 * and auto register components from current project. 037 */ 038public class ComponentExtension extends BaseComponentsHandler 039 implements BeforeAllCallback, AfterAllCallback, JUnit5InjectionSupport, BeforeEachCallback, AfterEachCallback { 040 041 public static final ExtensionContext.Namespace NAMESPACE = 042 ExtensionContext.Namespace.create(ComponentExtension.class.getName()); 043 044 private static final String USE_EACH_KEY = ComponentExtension.class.getName() + ".useEach"; 045 046 private static final String SHARED_INSTANCE = ComponentExtension.class.getName() + ".instance"; 047 048 @Override 049 public void beforeAll(final ExtensionContext extensionContext) { 050 final WithComponents element = AnnotationUtils 051 .findAnnotation(extensionContext.getElement(), WithComponents.class) 052 .orElseThrow(() -> new IllegalArgumentException( 053 "No annotation @WithComponents on " + extensionContext.getRequiredTestClass())); 054 this.packageName = element.value(); 055 if (element.isolatedPackages().length > 0) { 056 withIsolatedPackage(null, element.isolatedPackages()); 057 } 058 059 final boolean shouldUseEach = shouldIgnore(extensionContext.getElement()); 060 if (!shouldUseEach) { 061 doStart(extensionContext); 062 } else if (!extensionContext.getElement().map(AnnotatedElement::getAnnotations).map(annotations -> { 063 int componentIndex = -1; 064 for (int i = 0; i < annotations.length; i++) { 065 final Class<? extends Annotation> type = annotations[i].annotationType(); 066 if (type == WithComponents.class) { 067 componentIndex = i; 068 } else if (type == Environment.class && componentIndex >= 0) { 069 return false; 070 } 071 } 072 return true; 073 }).orElse(false)) { 074 // check the ordering, if environments are put after this then the context is likely wrong 075 // this condition is a simple heuristic but enough for most cases 076 throw new IllegalArgumentException("If you combine @WithComponents and @Environment, you must ensure " 077 + "environment annotations are becoming before the component one otherwise you will run in an " 078 + "unexpected context and will not reproduce real execution."); 079 } 080 extensionContext.getStore(NAMESPACE).put(USE_EACH_KEY, shouldUseEach); 081 extensionContext.getStore(NAMESPACE).put(SHARED_INSTANCE, this); 082 } 083 084 @Override 085 public void afterAll(final ExtensionContext extensionContext) { 086 if (!shouldUseEach(extensionContext)) { 087 doStop(extensionContext); 088 } 089 } 090 091 @Override 092 public Class<? extends Annotation> injectionMarker() { 093 return Injected.class; 094 } 095 096 @Override 097 public void beforeEach(final ExtensionContext extensionContext) { 098 if (!shouldUseEach(extensionContext)) { 099 doInject(extensionContext); 100 } 101 } 102 103 @Override 104 public void afterEach(final ExtensionContext extensionContext) { 105 if (!shouldUseEach(extensionContext)) { 106 resetState(); 107 } 108 } 109 110 public void doStart(final ExtensionContext extensionContext) { 111 extensionContext.getStore(NAMESPACE).put(EmbeddedComponentManager.class.getName(), start()); 112 } 113 114 public void doStop(final ExtensionContext extensionContext) { 115 ofNullable(EmbeddedComponentManager.class 116 .cast(extensionContext.getStore(NAMESPACE).get(EmbeddedComponentManager.class.getName()))) 117 .ifPresent(EmbeddedComponentManager::close); 118 } 119 120 public void doInject(final ExtensionContext extensionContext) { 121 extensionContext.getTestInstance().ifPresent(this::injectServices); 122 } 123 124 private Boolean shouldUseEach(final ExtensionContext extensionContext) { 125 return extensionContext.getStore(NAMESPACE).get(USE_EACH_KEY, boolean.class); 126 } 127 128 private boolean shouldIgnore(final Optional<AnnotatedElement> element) { 129 return !AnnotationUtils.findRepeatableAnnotations(element, Environment.class).isEmpty(); 130 } 131}