001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2018–2019 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.microprofile.config.cdi; 018 019import java.lang.annotation.Annotation; 020 021import java.lang.reflect.Executable; 022import java.lang.reflect.Member; 023import java.lang.reflect.Parameter; 024import java.lang.reflect.Type; 025 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Map; 033import java.util.Map.Entry; 034import java.util.Objects; 035import java.util.Set; 036 037import javax.enterprise.context.spi.CreationalContext; 038 039import javax.enterprise.event.Observes; 040 041import javax.enterprise.inject.spi.AfterBeanDiscovery; 042import javax.enterprise.inject.spi.AfterDeploymentValidation; 043import javax.enterprise.inject.spi.Annotated; 044import javax.enterprise.inject.spi.AnnotatedCallable; 045import javax.enterprise.inject.spi.AnnotatedField; 046import javax.enterprise.inject.spi.AnnotatedMethod; 047import javax.enterprise.inject.spi.AnnotatedParameter; 048import javax.enterprise.inject.spi.AnnotatedType; 049import javax.enterprise.inject.spi.Bean; 050import javax.enterprise.inject.spi.BeanManager; 051import javax.enterprise.inject.spi.DeploymentException; 052import javax.enterprise.inject.spi.Extension; 053import javax.enterprise.inject.spi.InjectionPoint; 054import javax.enterprise.inject.spi.ObserverMethod; 055import javax.enterprise.inject.spi.ProcessBean; 056import javax.enterprise.inject.spi.ProcessObserverMethod; 057 058import org.eclipse.microprofile.config.Config; 059 060import org.eclipse.microprofile.config.inject.ConfigProperty; 061 062/** 063 * An {@link Extension} that enables injection of {@link 064 * ConfigProperty}-annotated configuration property values. 065 * 066 * @author <a href="https://about.me/lairdnelson" 067 * target="_parent">Laird Nelson</a> 068 */ 069public final class ConfigExtension implements Extension { 070 071 private final Map<Set<Annotation>, Set<Type>> configPropertyTypes; 072 073 private final Set<InjectionPoint> configPropertyInjectionPointsToValidate; 074 075 private final Set<Set<Annotation>> allConfigQualifiers; 076 077 /** 078 * Creates a new {@link ConfigExtension}. 079 */ 080 public ConfigExtension() { 081 super(); 082 this.configPropertyInjectionPointsToValidate = new HashSet<>(); 083 this.configPropertyTypes = new HashMap<>(); 084 this.allConfigQualifiers = new HashSet<>(); 085 } 086 087 private final <X> void processBean(@Observes final ProcessBean<X> event) { 088 if (event != null) { 089 final Bean<X> bean = event.getBean(); 090 if (bean != null) { 091 final Set<InjectionPoint> beanInjectionPoints = bean.getInjectionPoints(); 092 if (beanInjectionPoints != null && !beanInjectionPoints.isEmpty()) { 093 for (final InjectionPoint beanInjectionPoint : beanInjectionPoints) { 094 if (beanInjectionPoint != null) { 095 final Set<Annotation> qualifiers = beanInjectionPoint.getQualifiers(); 096 assert qualifiers != null; 097 final Type type = beanInjectionPoint.getType(); 098 assert type != null; 099 if (Config.class.equals(type)) { 100 if (!qualifiers.isEmpty()) { 101 final Set<Annotation> configQualifiers = new HashSet<>(qualifiers); 102 if (configQualifiers.removeIf(q -> q instanceof ConfigProperty)) { 103 configQualifiers.add(ConfigPropertyLiteral.INSTANCE); 104 } 105 configQualifiers.add(AnyLiteral.INSTANCE); 106 allConfigQualifiers.add(configQualifiers); 107 } 108 } else { 109 final Annotated annotated = beanInjectionPoint.getAnnotated(); 110 if (annotated != null && annotated.isAnnotationPresent(ConfigProperty.class)) { 111 this.configPropertyInjectionPointsToValidate.add(beanInjectionPoint); 112 Set<Annotation> configPropertyQualifiers = new HashSet<>(qualifiers); 113 configPropertyQualifiers.removeIf(q -> q instanceof ConfigProperty); 114 configPropertyQualifiers.add(ConfigPropertyLiteral.INSTANCE); 115 configPropertyQualifiers = Collections.unmodifiableSet(configPropertyQualifiers); 116 Set<Type> configPropertyTypes = this.configPropertyTypes.get(configPropertyQualifiers); 117 if (configPropertyTypes == null) { 118 configPropertyTypes = new HashSet<>(); 119 this.configPropertyTypes.put(configPropertyQualifiers, configPropertyTypes); 120 } 121 configPropertyTypes.add(type); 122 } 123 } 124 } 125 } 126 } 127 } 128 } 129 } 130 131 private final <T, X> void processObserverMethod(@Observes final ProcessObserverMethod<T, X> event, 132 final BeanManager beanManager) { 133 if (event != null && beanManager != null) { 134 final AnnotatedMethod<X> annotatedMethod = event.getAnnotatedMethod(); 135 if (annotatedMethod != null) { 136 final List<AnnotatedParameter<X>> annotatedParameters = annotatedMethod.getParameters(); 137 if (annotatedParameters != null && annotatedParameters.size() > 1) { 138 for (final AnnotatedParameter<X> annotatedParameter : annotatedParameters) { 139 if (annotatedParameter != null && 140 !annotatedParameter.isAnnotationPresent(Observes.class)) { 141 final InjectionPoint injectionPoint = beanManager.createInjectionPoint(annotatedParameter); 142 assert injectionPoint != null; 143 final Type type = injectionPoint.getType(); 144 final Set<Annotation> qualifiers = injectionPoint.getQualifiers(); 145 if (Config.class.equals(type)) { 146 if (qualifiers != null && !qualifiers.isEmpty()) { 147 final Set<Annotation> configQualifiers = new HashSet<>(qualifiers); 148 if (configQualifiers.removeIf(q -> q instanceof ConfigProperty)) { 149 configQualifiers.add(ConfigPropertyLiteral.INSTANCE); 150 } 151 configQualifiers.add(AnyLiteral.INSTANCE); 152 allConfigQualifiers.add(Collections.unmodifiableSet(configQualifiers)); 153 } 154 } else if (annotatedParameter.isAnnotationPresent(ConfigProperty.class)) { 155 this.configPropertyInjectionPointsToValidate.add(injectionPoint); 156 Set<Annotation> configPropertyQualifiers = new HashSet<>(qualifiers); 157 configPropertyQualifiers.removeIf(q -> q instanceof ConfigProperty); 158 configPropertyQualifiers.add(ConfigPropertyLiteral.INSTANCE); 159 configPropertyQualifiers = Collections.unmodifiableSet(configPropertyQualifiers); 160 Set<Type> configPropertyTypes = this.configPropertyTypes.get(configPropertyQualifiers); 161 if (configPropertyTypes == null) { 162 configPropertyTypes = new HashSet<>(); 163 this.configPropertyTypes.put(configPropertyQualifiers, configPropertyTypes); 164 } 165 configPropertyTypes.add(type); 166 } 167 } 168 } 169 } 170 } 171 } 172 } 173 174 private final void afterBeanDiscovery(@Observes final AfterBeanDiscovery event, 175 final BeanManager beanManager) { 176 if (event != null) { 177 178 final AnnotatedType<Config> configAnnotatedType = beanManager.createAnnotatedType(Config.class); 179 assert configAnnotatedType != null; 180 181 final Set<Annotation> defaultConfigQualifiers = new HashSet<>(); 182 defaultConfigQualifiers.add(AnyLiteral.INSTANCE); 183 defaultConfigQualifiers.add(DefaultLiteral.INSTANCE); 184 this.allConfigQualifiers.add(defaultConfigQualifiers); 185 186 if (!this.configPropertyTypes.isEmpty()) { 187 final Set<Entry<Set<Annotation>, Set<Type>>> entrySet = this.configPropertyTypes.entrySet(); 188 assert entrySet != null; 189 assert !entrySet.isEmpty(); 190 for (final Entry<Set<Annotation>, Set<Type>> entry : entrySet) { 191 assert entry != null; 192 final Set<Annotation> qualifiers = entry.getKey(); 193 assert qualifiers != null; 194 final Set<Type> types = entry.getValue(); 195 assert types != null; 196 final Set<Type> newTypes = new HashSet<>(types); 197 final Iterator<Type> iterator = newTypes.iterator(); 198 assert iterator != null; 199 while (iterator.hasNext()) { 200 final Type type = iterator.next(); 201 assert type != null; 202 if (!noBeans(beanManager, type, qualifiers)) { 203 // A user-supplied bean is already present for the type 204 // and qualifiers so don't install a default bean. 205 iterator.remove(); 206 } 207 } 208 event.addBean(new ConfigPropertyBean<>(newTypes, qualifiers)); 209 } 210 } 211 212 for (final Set<Annotation> configQualifiers : this.allConfigQualifiers) { 213 if (noBeans(beanManager, Config.class, configQualifiers)) { 214 event.addBean(new ConfigBean(configQualifiers)); 215 } 216 } 217 218 } 219 this.allConfigQualifiers.clear(); 220 } 221 222 private final void afterDeploymentValidation(@Observes final AfterDeploymentValidation event, 223 final BeanManager beanManager) { 224 if (event != null && beanManager != null) { 225 final CreationalContext<?> cc = beanManager.createCreationalContext(null); 226 try { 227 for (final InjectionPoint injectionPoint : this.configPropertyInjectionPointsToValidate) { 228 assert injectionPoint != null; 229 if (beanManager.getInjectableReference(injectionPoint, cc) == null) { 230 event.addDeploymentProblem(new DeploymentException("No value exists for the mandatory configuration property named " + 231 getConfigPropertyName(injectionPoint))); 232 } 233 } 234 } finally { 235 cc.release(); 236 } 237 } 238 this.configPropertyInjectionPointsToValidate.clear(); 239 this.configPropertyTypes.clear(); 240 } 241 242 private static final boolean noBeans(final BeanManager beanManager, final Type type, final Set<Annotation> qualifiers) { 243 Objects.requireNonNull(beanManager); 244 Objects.requireNonNull(type); 245 final Collection<?> beans; 246 if (qualifiers == null || qualifiers.isEmpty()) { 247 beans = beanManager.getBeans(type); 248 } else { 249 beans = beanManager.getBeans(type, qualifiers.toArray(new Annotation[qualifiers.size()])); 250 } 251 return beans == null || beans.isEmpty(); 252 } 253 254 static final String getConfigPropertyName(final InjectionPoint injectionPoint) { 255 return getConfigPropertyName(injectionPoint, null); 256 } 257 258 static final String getConfigPropertyName(final InjectionPoint injectionPoint, ConfigProperty configProperty) { 259 String name = null; 260 if (injectionPoint != null) { 261 if (configProperty == null) { 262 final Collection<? extends Annotation> qualifiers = injectionPoint.getQualifiers(); 263 if (qualifiers != null) { 264 for (final Annotation qualifier : qualifiers) { 265 if (qualifier != null && ConfigProperty.class.equals(qualifier.annotationType())) { 266 configProperty = (ConfigProperty)qualifier; 267 break; 268 } 269 } 270 } 271 } 272 if (configProperty != null) { 273 name = configProperty.name(); 274 assert name != null; 275 if (name.isEmpty()) { 276 final Annotated annotated = injectionPoint.getAnnotated(); 277 if (annotated instanceof AnnotatedField) { 278 final AnnotatedField<?> field = (AnnotatedField<?>)annotated; 279 final Member member = field.getJavaMember(); 280 if (member != null) { 281 final AnnotatedType<?> declaringType = field.getDeclaringType(); 282 if (declaringType != null) { 283 name = declaringType.getJavaClass().getCanonicalName() + "." + member.getName(); 284 } 285 } 286 } else if (annotated instanceof AnnotatedParameter) { 287 final AnnotatedParameter<?> annotatedParameter = (AnnotatedParameter<?>)annotated; 288 // The specification says: "[The ConfigProperty name that 289 // will be synthesized if one is not provided] will be 290 // derived automatically as 291 // <class_name>.<injetion_point_name> [sic], where 292 // injection_point_name [sic] is the field name or 293 // parameter name, class_name is the fully qualified name 294 // of the class being injected to [sic]." 295 final AnnotatedCallable<?> declaringCallable = annotatedParameter.getDeclaringCallable(); 296 if (declaringCallable != null) { 297 final Member member = declaringCallable.getJavaMember(); 298 assert member instanceof Executable; 299 final Executable executable = (Executable)member; 300 final int position = annotatedParameter.getPosition(); 301 final Parameter[] parameters = executable.getParameters(); 302 if (parameters != null && parameters.length > position) { 303 final Parameter parameter = parameters[position]; 304 assert parameter != null; 305 if (parameter.isNamePresent()) { 306 final AnnotatedType<?> declaringType = declaringCallable.getDeclaringType(); 307 if (declaringType != null) { 308 name = declaringType.getJavaClass().getCanonicalName() + "." + parameter.getName(); 309 } 310 } 311 } 312 } 313 } else { 314 assert false; 315 } 316 } 317 } 318 } 319 return name; 320 } 321 322}