package act.app.conf;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* 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.
* #L%
*/
import act.app.App;
import act.app.AppByteCodeScanner;
import act.app.data.StringValueResolverManager;
import act.app.event.AppEventId;
import act.conf.AppConfig;
import act.event.AppEventListenerBase;
import act.util.AnnotatedTypeFinder;
import org.osgl.$;
import org.osgl.exception.NotAppliedException;
import org.osgl.inject.BeanSpec;
import org.osgl.inject.Injector;
import org.osgl.logging.LogManager;
import org.osgl.logging.Logger;
import org.osgl.util.Const;
import org.osgl.util.E;
import org.osgl.util.StringValueResolver;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.EventObject;
import java.util.Map;
import java.util.Set;
public class AutoConfigPlugin extends AnnotatedTypeFinder {
private static final Logger logger = LogManager.get(AutoConfigPlugin.class);
private static Field modifiersField;
public AutoConfigPlugin() {
super(true, false, AutoConfig.class, new $.F2<App, String, Map<Class<? extends AppByteCodeScanner>, Set<String>>>() {
@Override
public Map<Class<? extends AppByteCodeScanner>, Set<String>> apply(final App app, final String className) throws NotAppliedException, $.Break {
app.eventBus().bind(AppEventId.PRE_START, new AppEventListenerBase() {
@Override
public void on(EventObject event) throws Exception {
Class<?> autoConfigClass = $.classForName(className, app.classLoader());
new AutoConfigLoader(app, autoConfigClass).load();
}
});
return null;
}
});
}
private static void allowChangeFinalField() {
try {
modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
} catch (Exception e) {
throw E.unexpected(e);
}
}
private static void resetFinalFieldUpdate() {
try {
modifiersField.setAccessible(false);
} catch (Exception e) {
throw E.unexpected(e);
}
}
/**
* Used by plugin that to load their {@link AutoConfig auto configure class}
*
* @param autoConfigClass the class with {@link AutoConfig} annotation
* @param app the application instance
*/
public static void loadPluginAutoConfig(Class<?> autoConfigClass, App app) {
new AutoConfigLoader(app, autoConfigClass).load();
}
private static class AutoConfigLoader {
//private App app;
private AppConfig conf;
private Class<?> autoConfigClass;
private String ns;
private StringValueResolverManager resolverManager;
private Injector injector;
AutoConfigLoader(App app, Class<?> autoConfigClass) {
this.conf = app.config();
this.autoConfigClass = autoConfigClass;
this.ns = (autoConfigClass.getAnnotation(AutoConfig.class)).value();
this.resolverManager = app.resolverManager();
this.injector = app.injector();
synchronized (AutoConfigLoader.class) {
allowChangeFinalField();
app.jobManager().on(AppEventId.START, new Runnable() {
@Override
public void run() {
resetFinalFieldUpdate();
}
});
}
}
private boolean turnOffFinal(Field field) {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
try {
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} catch (Exception e) {
throw E.unexpected(e);
}
return true;
}
return false;
}
private void turnOnFinal(Field field) {
try {
modifiersField.setInt(field, field.getModifiers() | Modifier.FINAL);
} catch (Exception e) {
throw E.unexpected(e);
}
}
void load() {
loadClass(autoConfigClass, ns);
}
void loadClass(Class<?> c, String ns) {
Class[] ca = c.getClasses();
for (Class c0 : ca) {
int mod = c0.getModifiers();
if (Modifier.isStatic(mod)) {
loadClass(c0, ns + "." + c0.getSimpleName());
}
}
Field[] fa = c.getDeclaredFields();
for (Field f : fa) {
if (Modifier.isStatic(f.getModifiers())) {
loadAutoConfig_(f, ns);
}
}
}
private void loadAutoConfig_(Field f, String ns) {
String key = ns + "." + f.getName();
Object val = conf.getIgnoreCase(key);
if (null == val) {
// try to change the "prefix.key_x_an_y" form to "prefix.key.x.an.y" form
key = key.replace('_', '.');
val = conf.getIgnoreCase(key);
if (null == val) {
return;
}
}
BeanSpec spec = BeanSpec.of(f, injector);
boolean isFinal = false;
try {
isFinal = turnOffFinal(f);
setField(f, null, key, val, spec);
} catch (Exception e) {
throw E.invalidConfiguration(e, "Error get configuration " + key + ": " + e.getMessage());
} finally {
if (isFinal) {
turnOnFinal(f);
}
}
}
private void setField(Field f, Object host, String key, Object val, BeanSpec spec) throws Exception {
if (spec.isInstanceOf($.Val.class)) {
$.Val value = $.cast(f.get(host));
Field fVal = $.Var.class.getDeclaredField("v");
fVal.setAccessible(true);
BeanSpec spec0 = BeanSpec.of(spec.typeParams().get(0), null, injector);
setField(fVal, value, key, val, spec0);
fVal.setAccessible(false);
} else if (spec.isInstanceOf(Const.class)) {
Const value = $.cast(f.get(host));
Field fConst = Const.class.getDeclaredField("v");
fConst.setAccessible(true);
BeanSpec spec0 = BeanSpec.of(spec.typeParams().get(0), null, injector);
setField(fConst, value, key, val, spec0);
fConst.setAccessible(false);
} else if (spec.isInstanceOf(Collection.class)) {
BeanSpec spec0 = BeanSpec.of(spec.typeParams().get(0), null, injector);
StringValueResolver resolver = resolverManager.resolver(spec0.rawType(), spec);
if (null == resolver) {
logger.warn("Config[%s] field type[%s] not recognized", key, spec);
} else {
Collection col = (Collection) injector.get(spec.rawType());
String[] sa = val.toString().split(",");
for (String s : sa) {
col.add(resolver.resolve(s));
}
f.set(host, col);
}
} else {
StringValueResolver resolver = resolverManager.resolver(spec.rawType());
if (null == resolver) {
logger.warn("Config[%s] field type[%s] not recognized", key, spec.rawType());
} else {
f.set(host, resolver.resolve(val.toString()));
}
}
}
}
}