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;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Optional;
024import java.util.Queue;
025import java.util.WeakHashMap;
026import java.util.concurrent.ConcurrentLinkedQueue;
027import java.util.function.Consumer;
028import org.osgi.framework.BundleContext;
029import org.osgi.service.log.LogLevel;
030import org.osgi.service.log.LoggerFactory;
031
032/**
033 * Tracks the availability of an OSGi {@link LoggerFactory} service
034 * and keeps the logger facades up-to-date.
035 */
036public class LoggerFacadeManager extends ServiceResolver {
037
038    @SuppressWarnings("PMD.FieldNamingConventions")
039    private static final Collection<LoggerFacade> facades
040        = Collections.newSetFromMap(new WeakHashMap<>());
041    private static BundleContext context;
042    private static Queue<Consumer<BundleContext>> pendingContextOperations
043        = new ConcurrentLinkedQueue<>();
044    private static BufferingLoggerFactory bufferingFactory
045        = new BufferingLoggerFactory();
046    private static LoggerFactory loggerFactory = bufferingFactory;
047
048    /**
049     * Register the given facade for receiving updates when the
050     * logger factory changes. 
051     *
052     * @param loggerFacade the logger facade
053     */
054    public static void registerFacade(LoggerFacade loggerFacade) {
055        synchronized (LoggerFacadeManager.class) {
056            loggerFacade.loggerFactoryUpdated(
057                loggerFactory == bufferingFactory ? bufferingFactory // NOPMD
058                    : new FailSafeLoggerFactory(loggerFacade, loggerFactory));
059            facades.add(loggerFacade);
060        }
061    }
062
063    @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
064    private void updateLoggerFactory(LoggerFactory factory) {
065        synchronized (LoggerFacadeManager.class) {
066            loggerFactory = factory;
067            for (LoggerFacade facade : new ArrayList<>(facades)) {
068                facade.loggerFactoryUpdated(
069                    loggerFactory == bufferingFactory ? bufferingFactory // NOPMD
070                        : new FailSafeLoggerFactory(facade, loggerFactory));
071            }
072        }
073    }
074
075    @Override
076    public void start(BundleContext context) throws Exception {
077        synchronized (LoggerFacadeManager.class) {
078            LoggerFacadeManager.context = context;
079            // Process any pending operations that depend on a context.
080            while (true) {
081                Consumer<BundleContext> operation
082                    = pendingContextOperations.poll();
083                if (operation == null) {
084                    break;
085                }
086                operation.accept(context);
087            }
088        }
089        super.start(context);
090    }
091
092    @Override
093    protected void configure() {
094        Optional.ofNullable(
095            context.getProperty(BufferingLoggerFactory.LOG_THRESHOLD_PROPERTY))
096            .map(LogLevel::valueOf)
097            .ifPresent(th -> bufferingFactory.setThreshold(th));
098        Optional.ofNullable(
099            context.getProperty(BufferingLoggerFactory.BUFFER_SIZE_PROPERTY))
100            .map(Integer::parseInt)
101            .ifPresent(sz -> bufferingFactory.setBufferSize(sz));
102        addDependency(LoggerFactory.class);
103    }
104
105    @Override
106    protected void onResolved() {
107        LoggerFactory factory = get(LoggerFactory.class);
108        // Yes, it may happen that we get a new logging event
109        // before all buffered events have been flushed. But
110        // in order to prevent this, we'd have to put all
111        // logger usages in a synchronized block.
112        updateLoggerFactory(factory);
113        bufferingFactory.flush(factory);
114    }
115
116    @Override
117    protected void onRebound(String dependency) {
118        if (LoggerFactory.class.getName().equals(dependency)) {
119            updateLoggerFactory(get(LoggerFactory.class));
120        }
121    }
122
123    @Override
124    public void stop(BundleContext context) throws Exception {
125        synchronized (LoggerFacadeManager.class) {
126            LoggerFacadeManager.context = context;
127        }
128        super.stop(context);
129    }
130
131    /**
132     * Execute an operation that depends on the availability of the
133     * bundle context. If a context is available, the operation
134     * is executed at once. Else, it is delayed until the context
135     * becomes available.
136     *
137     * @param operation the operation
138     */
139    public static void contextOperation(Consumer<BundleContext> operation) {
140        synchronized (LoggerFacadeManager.class) {
141            if (context != null) {
142                operation.accept(context);
143                return;
144            }
145            pendingContextOperations.add(operation);
146        }
147    }
148
149    @Override
150    protected void onDissolving() {
151        updateLoggerFactory(bufferingFactory);
152    }
153
154}