/* * 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.araqne.log.api; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.araqne.api.PrimitiveConverter; import org.araqne.confdb.Config; import org.araqne.confdb.ConfigDatabase; import org.araqne.confdb.ConfigService; import org.araqne.confdb.Predicates; import org.araqne.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 LastStateService getLastStateService() { return (LastStateService) getRequiredService(LastStateService.class); } @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"); started = false; unloadLoggers(); } @Override public boolean isAvailable() { return started; } private void loadLoggers() { Collection<LoggerConfig> configs = getLoggerConfigs(); for (LoggerConfig config : configs) { slog.info("araqne log api: trying to load logger [{}]", config); tryLoad(config); } } private void unloadLoggers() { // unload from registry LoggerRegistry loggerRegistry = getLoggerRegistry(); for (Logger logger : loggers.values()) { // stop and unregister if (logger.isRunning()) logger.stop(LoggerStopReason.FACTORY_DEPENDENCY, 5000); if (loggerRegistry != null) loggerRegistry.removeLogger(logger); // remove sync logger.removeEventListener(sync); } loggers.clear(); } private void tryLoad(LoggerConfig config) { // if logger already exists LoggerRegistry loggerRegistry = getLoggerRegistry(); if (loggerRegistry.getLogger(config.getFullname()) != null) { slog.warn("araqne log api: logger [{}] already registered, skip auto-load", config.getFullname()); return; } // try migration if (config.getVersion() == 1) migrateToVer2(config); LastStateService lss = getLastStateService(); LastState state = lss.getState(config.getFullname()); try { LoggerSpecification spec = new LoggerSpecification(config.getNamespace(), config.getName(), config.getDescription(), config.getConfigs()); Logger newLogger = handleNewLogger(spec, true); if (state == null) return; slog.info("araqne log api: logger [{}] is loaded", config.getFullname()); if (!config.isManualStart() && state.isEnabled() && !newLogger.isPending()) { newLogger.start(LoggerStartReason.SYSTEM_REQUEST, state.getInterval()); slog.info("araqne log api: logger [{}] started with interval {}ms", config.getFullname(), state.getInterval()); } } catch (Exception e) { slog.error(String.format("araqne log api: cannot load logger %s", config.getFullname()), e); } } @SuppressWarnings("deprecation") private void migrateToVer2(LoggerConfig config) { config.setVersion(2); LastState state = new LastState(); state.setLoggerName(config.getFullname()); state.setInterval(config.getInterval()); state.setEnabled(config.isRunning()); state.setRunning(config.isRunning()); state.setPending(config.isPending()); state.setLogCount(config.getCount()); state.setDropCount(0); state.setLastLogDate(null); LastStateService lss = getLastStateService(); lss.setState(state); slog.info("araqne log api: migrated logger [{}] state to v2", config.getFullname()); } private LogTransformerRegistry getTransformerRegistry() { return (LogTransformerRegistry) getRequiredService(LogTransformerRegistry.class); } private LoggerRegistry getLoggerRegistry() { return (LoggerRegistry) getRequiredService(LoggerRegistry.class); } private Object getRequiredService(Class<?> clazz) { ServiceReference ref = bc.getServiceReference(clazz.getName()); if (ref != null) return bc.getService(ref); return null; } 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("araqne-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 Logger newLogger(LoggerSpecification spec) { return handleNewLogger(spec, false); } private Logger handleNewLogger(LoggerSpecification spec, boolean booting) { Map<String, String> config = spec.getConfig(); Logger logger = createLogger(spec); boolean loggerPending = false; LoggerRegistry loggerRegistry = getLoggerRegistry(); for (String waitingFullName : loggerRegistry.getDependencies(logger.getFullName())) { Logger waiting = loggerRegistry.getLogger(waitingFullName); if (waiting == null) { slog.debug("araqne log api: logger [{}] is pending for logger [{}] is loaded", logger.getFullName(), waitingFullName); loggerPending = true; logger.addUnresolvedLogger(waitingFullName); } else if ((waiting.isEnabled()) && (!waiting.isRunning())) { slog.debug("araqne log api: logger [{}] is pending for logger [{}] is started", logger.getFullName(), waitingFullName); logger.addUnresolvedLogger(waitingFullName); } } boolean transformerPending = setupTransformer(logger); logger.setPending(loggerPending || transformerPending); loggers.put(logger.getFullName(), logger); // add listener, save config, and register logger logger.addEventListener(sync); if (!booting) saveLoggerConfig(logger, spec.getConfig()); loggerRegistry.addLogger(logger); for (LoggerFactoryEventListener callback : callbacks) { callback.loggerCreated(this, logger, config); } return logger; } private boolean setupTransformer(Logger logger) { Map<String, String> config = logger.getConfigs(); boolean transformerPending = false; LogTransformerRegistry transformerRegistry; if (config.containsKey("transformer")) { String transformerName = (String) config.get("transformer"); transformerRegistry = getTransformerRegistry(); LogTransformer transformer = null; if (transformerName != null) { transformerPending = true; if (transformerRegistry.getProfile(transformerName) != null) { try { transformer = transformerRegistry.newTransformer(transformerName); logger.setTransformer(transformer); transformerPending = false; } catch (LogTransformerNotReadyException e) { slog.debug("araqne log api: cannot load transformer [" + transformerName + "] for logger [" + logger.getFullName() + "]", e.getCause()); } catch (Throwable t) { slog.warn("araqne log api: cannot load transformer [" + transformerName + "] for logger [" + logger.getFullName() + "]", t); } } } } return transformerPending; } @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); if (logger.isRunning()) throw new IllegalStateException("logger is still running: " + fullName); // remove listener, remove from logger registry, and delete config LoggerRegistry loggerRegistry = getLoggerRegistry(); loggerRegistry.removeLogger(logger); logger.removeEventListener(sync); deleteLoggerConfig(logger); loggers.remove(fullName); LastStateService lss = getLastStateService(); lss.deleteState(fullName); for (LoggerFactoryEventListener callback : callbacks) { try { callback.loggerDeleted(this, logger); } catch (Exception e) { slog.error("araqne 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 List<Locale> getLocales() { return Arrays.asList(Locale.ENGLISH); } @Override public String getDisplayGroup(Locale locale) { return null; } @Override public Collection<Locale> getDisplayNameLocales() { return getLocales(); } @Override public Collection<Locale> getDescriptionLocales() { return getLocales(); } 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, Map<String, String> config) { LoggerConfig model = new LoggerConfig(logger); model.setVersion(2); 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("araqne log api: created logger [{}] config, {}", logger.getFullName(), logger.isRunning()); } else { if (!PrimitiveConverter.serialize(model).equals(c.getDocument())) { db.update(c, model); slog.trace("araqne 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("araqne log api: deleted logger config for [{}]", logger.getFullName()); } private class DbSync extends AbstractLoggerEventListener { @Override public void onStart(Logger logger) { LastState s = buildState(logger); LastStateService lss = getLastStateService(); lss.setState(s); } @Override public void onStop(Logger logger, LoggerStopReason reason) { LastState s = buildState(logger); // isRunning() returns true while stopping s.setRunning(false); LastStateService lss = getLastStateService(); lss.setState(s); } @Override public void onSetTimeRange(Logger logger) { LastState s = buildState(logger); LastStateService lss = getLastStateService(); lss.setState(s); } @Override public void onUpdated(Logger logger, Map<String, String> config) { saveLoggerConfig(logger, config); } private LastState buildState(Logger logger) { LastState s = new LastState(); s.setLoggerName(logger.getFullName()); s.setInterval(logger.getInterval()); if (logger.getTimeRange() != null) { s.setStartTime(logger.getTimeRange().getStartTime()); s.setEndTime(logger.getTimeRange().getEndTime()); } s.setLogCount(logger.getLogCount()); s.setDropCount(logger.getDropCount()); s.setLastLogDate(logger.getLastLogDate()); s.setPending(logger.isPending()); s.setProperties(logger.getStates()); s.setEnabled(logger.isEnabled()); s.setRunning(logger.isRunning()); s.setLogVolume(logger.getLogVolume()); s.setDropVolume(logger.getDropVolume()); return s; } } }