/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.deltaspike.core.impl.config; import org.apache.deltaspike.core.api.config.ConfigProperty; import org.apache.deltaspike.core.api.config.ConfigResolver; import org.apache.deltaspike.core.api.config.Configuration; import org.apache.deltaspike.core.spi.config.BaseConfigPropertyProducer; import org.apache.deltaspike.core.util.metadata.builder.ContextualLifecycle; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Bean; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static java.util.concurrent.TimeUnit.MILLISECONDS; class ProxyConfigurationLifecycle implements ContextualLifecycle { private Class<?>[] api; ProxyConfigurationLifecycle(final Class<?> proxyType) { this.api = new Class<?>[]{proxyType}; } @Override public Object create(final Bean bean, final CreationalContext creationalContext) { // TODO: support partialbean binding? can make sense for virtual properties + would integrate with jcache // we'd need to add @PartialBeanBinding on a bean created from ConfigurationHandler // detection can just be a loadClass of this API // for now: waiting for user request for it final Configuration configuration = api[0].getAnnotation(Configuration.class); final long cacheFor = configuration.cacheFor(); return Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), api, new ConfigurationHandler( cacheFor <= 0 ? -1 : configuration.cacheUnit().toMillis(cacheFor), configuration.prefix())); } @Override public void destroy(final Bean bean, final Object instance, final CreationalContext creationalContext) { // no-op } private static final class ConfigurationHandler implements InvocationHandler { private final BaseConfigPropertyProducer delegate = new BaseConfigPropertyProducer() { }; private final ConcurrentMap<Method, ConfigResolver.TypedResolver<?>> resolvers = new ConcurrentHashMap<Method, ConfigResolver.TypedResolver<?>>(); private final long cacheMs; private final String prefix; private ConfigurationHandler(final long cacheMs, final String prefix) { this.cacheMs = cacheMs; this.prefix = prefix; } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (Object.class == method.getDeclaringClass()) { try { return method.invoke(this, args); } catch (final InvocationTargetException ite) { throw ite.getCause(); } } ConfigResolver.TypedResolver<?> typedResolver = resolvers.get(method); if (typedResolver == null) { final ConfigProperty annotation = method.getAnnotation(ConfigProperty.class); if (annotation == null) { throw new UnsupportedOperationException( method + " doesn't have @ConfigProperty and therefore is illegal"); } // handle primitive bridge there (cdi doesnt support primitives but no reason our proxies don't) Class<?> returnType = method.getReturnType(); if (int.class == returnType) { returnType = Integer.class; } else if (long.class == returnType) { returnType = Long.class; } else if (boolean.class == returnType) { returnType = Boolean.class; } else if (short.class == returnType) { returnType = Short.class; } else if (byte.class == returnType) { returnType = Byte.class; } else if (float.class == returnType) { returnType = Float.class; } else if (double.class == returnType) { returnType = Double.class; } typedResolver = delegate.asResolver( prefix + annotation.name(), annotation.defaultValue(), returnType, annotation.converter(), annotation.parameterizedBy(), annotation.projectStageAware(), annotation.evaluateVariables()); if (cacheMs > 0) { typedResolver.cacheFor(MILLISECONDS, cacheMs); } final ConfigResolver.TypedResolver<?> existing = resolvers.putIfAbsent(method, typedResolver); if (existing != null) { typedResolver = existing; } } return typedResolver.getValue(); } } }