/* * Copyright 2010 NCHOVY * * 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. */ package org.krakenapps.log.api; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.krakenapps.api.PrimitiveConverter; import org.krakenapps.confdb.Config; import org.krakenapps.confdb.ConfigDatabase; import org.krakenapps.confdb.ConfigService; import org.krakenapps.confdb.Predicates; import org.krakenapps.log.api.impl.LoggerConfig; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; public abstract class AbstractLoggerFactory implements LoggerFactory { private final org.slf4j.Logger slog = org.slf4j.LoggerFactory.getLogger(AbstractLoggerFactory.class.getName()); private String namespace; private String fullName; private Map<String, Logger> loggers; private Set<LoggerFactoryEventListener> callbacks; private BundleContext bc; private boolean started; private DbSync sync = new DbSync();; public AbstractLoggerFactory() { this("local"); } public AbstractLoggerFactory(String namespace) { this.namespace = namespace; loggers = new ConcurrentHashMap<String, Logger>(); callbacks = Collections.newSetFromMap(new ConcurrentHashMap<LoggerFactoryEventListener, Boolean>()); } @Override public void onStart(BundleContext bc) { if (started) throw new IllegalStateException("logger factory [" + fullName + "] is already started"); this.bc = bc; loadLoggers(); this.started = true; } @Override public void onStop() { if (!started) throw new IllegalStateException("logger factory [" + fullName + "] is not started"); unloadLoggers(); started = false; } private void loadLoggers() { Collection<LoggerConfig> configs = getLoggerConfigs(); for (LoggerConfig config : configs) { slog.info("kraken log api: trying to load logger [{}]", config); tryLoad(config); } } private void unloadLoggers() { // unload from registry LoggerRegistry loggerRegistry = getLoggerRegistry(); for (Logger logger : loggers.values()) { // prevent stop state saving logger.removeEventListener(sync); // stop and unregister logger.stop(5000); if (loggerRegistry != null) loggerRegistry.removeLogger(logger); } loggers.clear(); } private void tryLoad(LoggerConfig config) { // if logger already exists LoggerRegistry loggerRegistry = getLoggerRegistry(); if (loggerRegistry.getLogger(config.getFullname()) != null) { slog.warn("kraken log api: logger [{}] already registered, skip auto-load", config.getFullname()); return; } try { Logger newLogger = handleNewLogger(config.getNamespace(), config.getName(), config.getDescription(), config.getCount(), config.getLastLogDate(), config.getConfigs(), true); newLogger.setPassive(config.isPassive()); slog.info("kraken log api: logger [{}] is loaded", config.getFullname()); if (config.isRunning() && config.getInterval() != -1) { newLogger.start(config.getInterval()); slog.info("kraken log api: logger [{}] started with interval {}ms", config.getFullname(), config.getInterval()); } } catch (Exception e) { slog.error(String.format("kraken log api: cannot load logger %s, saved config deleted.", config.getFullname()), e); } } private LoggerRegistry getLoggerRegistry() { ServiceReference ref = bc.getServiceReference(LoggerRegistry.class.getName()); if (ref == null) return null; return (LoggerRegistry) bc.getService(ref); } private Collection<LoggerConfig> getLoggerConfigs() { ConfigDatabase db = getConfigDatabase(); Map<String, Object> pred = new HashMap<String, Object>(); pred.put("factory_namespace", getNamespace()); pred.put("factory_name", getName()); return db.find(LoggerConfig.class, Predicates.field(pred)).getDocuments(LoggerConfig.class); } private ConfigDatabase getConfigDatabase() { ServiceReference ref = bc.getServiceReference(ConfigService.class.getName()); ConfigService conf = (ConfigService) bc.getService(ref); ConfigDatabase db = conf.ensureDatabase("kraken-log-api"); return db; } @Override public String getFullName() { if (fullName == null) fullName = namespace + "\\" + getName(); return fullName; } @Override public final String getNamespace() { return namespace; } @Override public final void addListener(LoggerFactoryEventListener callback) { callbacks.add(callback); } @Override public final void removeListener(LoggerFactoryEventListener callback) { callbacks.remove(callback); } @Override public final Logger newLogger(String name, String description, Properties config) { return newLogger("local", name, description, config); } @Override public final Logger newLogger(String namespace, String name, String description, Properties config) { return newLogger(namespace, name, description, 0, null, config); } @Override public Logger newLogger(String namespace, String name, String description, long logCount, Date lastLogDate, Properties config) { return handleNewLogger(namespace, name, description, logCount, lastLogDate, config, false); } private Logger handleNewLogger(String namespace, String name, String description, long logCount, Date lastLogDate, Properties config, boolean booting) { Logger logger = createLogger(new LoggerSpecification(namespace, name, description, logCount, lastLogDate, config)); loggers.put(logger.getFullName(), logger); // add listener, save config, and register logger logger.addEventListener(sync); if (!booting) saveLoggerConfig(logger, config); LoggerRegistry loggerRegistry = getLoggerRegistry(); loggerRegistry.addLogger(logger); for (LoggerFactoryEventListener callback : callbacks) { callback.loggerCreated(this, logger, config); } return logger; } @Override public void deleteLogger(String name) { deleteLogger("local", name); } @Override public void deleteLogger(String namespace, String name) { String fullName = namespace + "\\" + name; Logger logger = loggers.get(fullName); if (logger == null) throw new IllegalStateException("logger not found: " + fullName); // remove listener, remove from logger registry, and delete config logger.removeEventListener(sync); LoggerRegistry loggerRegistry = getLoggerRegistry(); loggerRegistry.removeLogger(logger); deleteLoggerConfig(logger); for (LoggerFactoryEventListener callback : callbacks) { try { callback.loggerDeleted(this, logger); } catch (Exception e) { slog.error("kraken log api: logger factory event listener should not throw any exception", e); } } } // // default empty implementation // @Override public Collection<LoggerConfigOption> getConfigOptions() { return new ArrayList<LoggerConfigOption>(); } @Override public Collection<Locale> getDisplayNameLocales() { return Arrays.asList(Locale.ENGLISH); } @Override public Collection<Locale> getDescriptionLocales() { return Arrays.asList(Locale.ENGLISH); } protected abstract Logger createLogger(LoggerSpecification spec); @Override public String toString() { return String.format("fullname=%s, type=%s, description=%s", getFullName(), getDisplayName(Locale.ENGLISH), getDescription(Locale.ENGLISH)); } private void saveLoggerConfig(Logger logger, Properties config) { LoggerConfig model = new LoggerConfig(logger); model.setConfigs(config); ConfigDatabase db = getConfigDatabase(); Config c = db.findOne(LoggerConfig.class, Predicates.field("fullname", logger.getFullName())); if (c == null) { db.add(model); slog.trace("kraken log api: created logger [{}] config, {}", logger.getFullName(), logger.isRunning()); } else { if (!PrimitiveConverter.serialize(model).equals(c.getDocument())) { db.update(c, model); slog.trace("kraken log api: updated logger [{}] config, {}", logger.getFullName(), logger.isRunning()); } } } private void deleteLoggerConfig(Logger logger) { ConfigDatabase db = getConfigDatabase(); Config c = db.findOne(LoggerConfig.class, Predicates.field("fullname", logger.getFullName())); if (c != null) db.remove(c); slog.info("kraken log api: deleted logger config for [{}]", logger.getFullName()); } private class DbSync implements LoggerEventListener { @Override public void onStart(Logger logger) { ConfigDatabase db = getConfigDatabase(); Config c = db.findOne(LoggerConfig.class, Predicates.field("fullname", logger.getFullName())); if (c == null) { slog.warn("kraken log api: config not exists for logger {}", logger.getFullName()); return; } LoggerConfig model = c.getDocument(LoggerConfig.class); model.setRunning(true); model.setInterval(logger.getInterval()); db.update(c, model); slog.trace("kraken log api: running status saved: {}", logger.getFullName()); } @Override public void onStop(Logger logger) { ConfigDatabase db = getConfigDatabase(); Config c = db.findOne(LoggerConfig.class, Predicates.field("fullname", logger.getFullName())); if (c == null) { slog.warn("kraken log api: config not exists for logger {}", logger.getFullName()); return; } LoggerConfig model = c.getDocument(LoggerConfig.class); // do not save status caused by bundle stopping LoggerRegistry loggerRegistry = getLoggerRegistry(); if (loggerRegistry != null && loggerRegistry.isOpen()) { slog.trace("kraken log api: [{}] stopped state saved", logger.getFullName()); model.setRunning(false); } model.setCount(logger.getLogCount()); model.setLastLogDate(logger.getLastLogDate()); db.update(c, model); } @Override public void onUpdated(Logger logger, Properties config) { saveLoggerConfig(logger, config); } } }