package act.app;
/*-
* #%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.Act;
import act.Destroyable;
import act.app.event.AppEventId;
import act.conf.AppConfig;
import act.db.*;
import act.db.util.SequenceNumberGenerator;
import act.db.util._SequenceNumberGenerator;
import act.event.AppEventListenerBase;
import act.util.ClassNode;
import act.util.General;
import org.osgl.$;
import org.osgl.exception.ConfigurationException;
import org.osgl.logging.LogManager;
import org.osgl.logging.Logger;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.S;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.lang.annotation.Annotation;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;
@ApplicationScoped
public class DbServiceManager extends AppServiceBase<DbServiceManager> implements DaoLocator {
private static Logger logger = LogManager.get(DbServiceManager.class);
public static final String DEFAULT = "default";
// map service id to service instance
private Map<String, DbService> serviceMap = C.newMap();
// map model class to dao class
private Map<Class<?>, Dao> modelDaoMap = C.newMap();
@Inject
public DbServiceManager(final App app) {
super(app);
EntityClassRepository.init(app);
initServices(app.config());
configureSequenceGenerator(app);
app.eventBus().bind(AppEventId.SINGLETON_PROVISIONED, new AppEventListenerBase() {
@Override
public void on(EventObject event) throws Exception {
ClassNode node = app.classLoader().classInfoRepository().node(Dao.class.getName());
node.visitPublicNotAbstractTreeNodes(new $.Visitor<ClassNode>() {
private boolean isGeneral(Class c) {
Annotation[] aa = c.getDeclaredAnnotations();
for (Annotation a : aa) {
if (a instanceof General) {
return true;
}
}
return false;
}
@Override
public void visit(ClassNode classNode) throws $.Break {
Class<? extends Dao> daoType = $.classForName(classNode.name(), app.classLoader());
if (isGeneral(daoType)) {
return;
}
try {
Dao dao = $.cast(app.getInstance(daoType));
Class<?> modelType = dao.modelType();
DB db = modelType.getAnnotation(DB.class);
String svcId = null == db ? DEFAULT : db.value();
DbService dbService = dbService(svcId);
E.invalidConfigurationIf(null == dbService, "cannot find db service by id: %s", svcId);
dao = dbService.newDaoInstance(daoType);
modelDaoMap.put(modelType, dao);
} catch (Exception e) {
logger.warn(e, "error loading DAO: %s", daoType);
}
}
});
}
});
}
private void configureSequenceGenerator(final App app) {
app.jobManager().on(AppEventId.DEPENDENCY_INJECTOR_PROVISIONED, new Runnable() {
@Override
public void run() {
_SequenceNumberGenerator seqGen = app.config().sequenceNumberGenerator();
seqGen.configure(app.config(), DbServiceManager.this);
SequenceNumberGenerator.registerImpl(seqGen);
}
});
}
@Override
protected void releaseResources() {
Destroyable.Util.tryDestroyAll(serviceMap.values(), ApplicationScoped.class);
serviceMap.clear();
Destroyable.Util.tryDestroyAll(modelDaoMap.values(), ApplicationScoped.class);
modelDaoMap.clear();
}
@Override
public Dao dao(Class<?> modelClass) {
Dao dao = modelDaoMap.get(modelClass);
if (null == dao) {
String svcId = dbId(modelClass);
DbService dbService = dbService(svcId);
dao = dbService.defaultDao(modelClass);
modelDaoMap.put(modelClass, dao);
}
return dao;
}
public <T extends DbService> T dbService(String id) {
return (T)serviceMap.get(id);
}
public Iterable<DbService> registeredServices() {
return serviceMap.values();
}
private void initServices(AppConfig config) {
DbManager dbManager = Act.dbManager();
if (!dbManager.hasPlugin()) {
logger.warn("DB service not initialized: No DB plugin found");
return;
}
DbPlugin db = dbManager.theSolePlugin();
Map<String, String> dbConf = config.subSet("db.");
if (dbConf.isEmpty()) {
if (null == db) {
logger.warn("DB service not intialized: need to specify default db service implementation");
return;
} else {
logger.warn("DB configuration not found. Will try to init default service with the sole db plugin: %s", db);
DbService svc = db.initDbService(DEFAULT, app(), new HashMap<String, String>());
serviceMap.put(DEFAULT, svc);
return;
}
}
String firstInstance = null;
if (dbConf.containsKey("db.instances")) {
String instances = dbConf.get("db.instances").toString();
String[] sa = instances.split("[,\\s;:]+");
for (String dbId: sa) {
initService(dbId, dbConf);
}
firstInstance = sa[0];
}
if (serviceMap.containsKey(DEFAULT)) return;
// try init default service if conf found
String dbId = null;
if (dbConf.containsKey("db." + DEFAULT +".impl")) {
dbId = DEFAULT;
} else if (dbConf.containsKey("db.impl")) {
dbId = "";
}
if (null != dbId) {
initService(dbId, dbConf);
} else if (serviceMap.size() == 1) {
DbService svc = serviceMap.values().iterator().next();
serviceMap.put(DEFAULT, svc);
logger.warn("db service configuration not found. Use the sole one db service[%s] as default service", svc.id());
} else {
if (serviceMap.isEmpty()) {
if (null == db) {
logger.warn("DB service not initialized: need to specify default db service implementation");
} else {
logger.warn("DB configuration not found. Will try to init default service with the sole db plugin: %s", db);
Map<String, String> svcConf = C.newMap();
String prefix = "db.";
for (String key : dbConf.keySet()) {
if (key.startsWith(prefix)) {
String o = dbConf.get(key);
svcConf.put(key.substring(prefix.length()), o);
}
}
DbService svc = db.initDbService(DEFAULT, app(), svcConf);
serviceMap.put(DEFAULT, svc);
}
} else {
logger.warn("Default service not specified. Use the first db instance as default service: %s", firstInstance);
serviceMap.put(DEFAULT, serviceMap.get(firstInstance));
}
}
}
private void initService(String dbId, Map<String, String> conf) {
Map<String, String> svcConf = C.newMap();
String prefix = "db." + (S.blank(dbId) ? "" : dbId + ".");
for (String key : conf.keySet()) {
if (key.startsWith(prefix)) {
String o = conf.get(key);
svcConf.put(key.substring(prefix.length()), o);
}
}
String impl = svcConf.remove("impl");
String svcId = S.empty(dbId) ? DEFAULT : dbId;
if (null == impl) {
throw new ConfigurationException("Cannot init db service[%s]: implementation not specified", svcId);
}
DbPlugin plugin = Act.dbManager().plugin(impl);
if (null == plugin) {
throw new ConfigurationException("Cannot init db service[%s]: implementation not found: %s", svcId, impl);
}
DbService svc = plugin.initDbService(S.blank(dbId) ? DEFAULT : dbId, app(), svcConf);
serviceMap.put(svcId, svc);
logger.info("db service[%s] initialized", svcId);
}
public static String dbId(Class<?> modelClass) {
DB db = modelClass.getAnnotation(DB.class);
if (null != db) {
return db.value();
}
return DbServiceManager.DEFAULT;
}
}