001/*
002 * Copyright (C) 2019 Michael N. Lipp (http://www.mnl.de)
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package de.mnl.osgi.lf4osgi.core;
018
019import de.mnl.osgi.coreutils.ServiceResolver;
020
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Optional;
025import java.util.Queue;
026import java.util.WeakHashMap;
027import java.util.concurrent.ConcurrentLinkedQueue;
028import java.util.function.Consumer;
029
030import org.osgi.framework.BundleContext;
031import org.osgi.service.log.LogLevel;
032import org.osgi.service.log.LoggerFactory;
033
034/**
035 * Tracks the availability of an OSGi {@link LoggerFactory} service
036 * and keeps the logger facades up-to-date.
037 */
038public class LoggerFacadeManager extends ServiceResolver {
039
040    @SuppressWarnings("PMD.FieldNamingConventions")
041    private static final Collection<LoggerFacade> facades
042        = Collections.newSetFromMap(new WeakHashMap<>());
043    private static BundleContext context;
044    private static Queue<Consumer<BundleContext>> pendingContextOperations
045        = new ConcurrentLinkedQueue<>();
046    private static BufferingLoggerFactory bufferingFactory
047        = new BufferingLoggerFactory();
048    private static LoggerFactory loggerFactory = bufferingFactory;
049
050    /**
051     * Register the given facade for receiving updates when the
052     * logger factory changes. 
053     *
054     * @param loggerFacade the logger facade
055     */
056    public static void registerFacade(LoggerFacade loggerFacade) {
057        synchronized (LoggerFacadeManager.class) {
058            loggerFacade.loggerFactoryUpdated(
059                loggerFactory == bufferingFactory ? bufferingFactory // NOPMD
060                    : new FailSafeLoggerFactory(loggerFacade, loggerFactory));
061            facades.add(loggerFacade);
062        }
063    }
064
065    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
066    private void updateLoggerFactory(LoggerFactory factory) {
067        synchronized (LoggerFacadeManager.class) {
068            loggerFactory = factory;
069            for (LoggerFacade facade : new ArrayList<>(facades)) {
070                facade.loggerFactoryUpdated(
071                    loggerFactory == bufferingFactory ? bufferingFactory // NOPMD
072                        : new FailSafeLoggerFactory(facade, loggerFactory));
073            }
074        }
075    }
076
077    @Override
078    public void start(BundleContext context) throws Exception {
079        synchronized (LoggerFacadeManager.class) {
080            LoggerFacadeManager.context = context;
081            // Process any pending operations that depend on a context.
082            while (true) {
083                Consumer<BundleContext> operation
084                    = pendingContextOperations.poll();
085                if (operation == null) {
086                    break;
087                }
088                operation.accept(context);
089            }
090        }
091        super.start(context);
092    }
093
094    @Override
095    protected void configure() {
096        Optional.ofNullable(
097            context.getProperty(BufferingLoggerFactory.LOG_THRESHOLD_PROPERTY))
098            .map(LogLevel::valueOf)
099            .ifPresent(th -> bufferingFactory.setThreshold(th));
100        Optional.ofNullable(
101            context.getProperty(BufferingLoggerFactory.BUFFER_SIZE_PROPERTY))
102            .map(Integer::parseInt)
103            .ifPresent(sz -> bufferingFactory.setBufferSize(sz));
104        addDependency(LoggerFactory.class);
105    }
106
107    @Override
108    protected void onResolved() {
109        LoggerFactory factory = get(LoggerFactory.class);
110        // Yes, it may happen that we get a new logging event
111        // before all buffered events have been flushed. But
112        // in order to prevent this, we'd have to put all
113        // logger usages in a synchronized block.
114        updateLoggerFactory(factory);
115        bufferingFactory.flush(factory);
116    }
117
118    @Override
119    protected void onRebound(String dependency) {
120        if (LoggerFactory.class.getName().equals(dependency)) {
121            updateLoggerFactory(get(LoggerFactory.class));
122        }
123    }
124
125    @Override
126    public void stop(BundleContext context) throws Exception {
127        synchronized (LoggerFacadeManager.class) {
128            LoggerFacadeManager.context = context;
129        }
130        super.stop(context);
131    }
132
133    /**
134     * Execute an operation that depends on the availability of the
135     * bundle context. If a context is available, the operation
136     * is executed at once. Else, it is delayed until the context
137     * becomes available.
138     *
139     * @param operation the operation
140     */
141    public static void contextOperation(Consumer<BundleContext> operation) {
142        synchronized (LoggerFacadeManager.class) {
143            if (context != null) {
144                operation.accept(context);
145                return;
146            }
147            pendingContextOperations.add(operation);
148        }
149    }
150
151    @Override
152    protected void onDissolving() {
153        updateLoggerFactory(bufferingFactory);
154    }
155
156}