blob: d5cd00876f619d63fa0c46c5e4ebc9516b08906d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.felix.scr.impl.logger;
import java.io.Closeable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.Logger;
import org.osgi.service.log.LoggerFactory;
import org.osgi.util.tracker.ServiceTracker;
/*
* A class that tracks the LoggerFactory of a bundle and its associated Logger objects.
* When a bundle is stopped and/or the service is unregistered, the logger objects are cleaned.
*
* The basic technique is to use a facade. Instead of returning a log object, we return a facade. The
* methods delegate to the actual logger object. If there is no logger object, we create one.
*
* The LogDomain represents every bundle. Per LogDomain, we keep the facades. If the factory goes,
* we reset the facades.
*/
class LogManager extends ServiceTracker<Object, Object> implements BundleListener {
private static final String LOGGER_FACTORY_CLASS_NAME = "org.osgi.service.log.LoggerFactory";
final BundleContext scrContext;
final AtomicBoolean closed = new AtomicBoolean(false);
/*
* Locks access to guarded fields
*/
class Lock {
final Map<Bundle, LogDomain> domains = new HashMap<>();
int trackingCount;
Object factory;
int ranking=0;
synchronized LogDomain getLogDomain(Bundle bundle) {
LogDomain domain = domains.get(bundle);
if (domain == null) {
domain = new LogDomain(bundle);
domains.put(bundle, domain);
}
return domain;
}
synchronized void removedFactory(Object service) {
if (this.factory == service) {
this.factory = null;
reset();
}
}
synchronized void setFactory(int ranking, Object service) {
if (this.factory == null) {
this.factory = service;
this.ranking = ranking;
} else if (this.ranking < ranking) {
this.factory = service;
this.ranking = ranking;
reset();
}
}
synchronized void reset() {
for (LogDomain domain : domains.values()) {
domain.reset();
}
}
synchronized Object getLogger(LoggerFacade facade, Bundle bundle, String name) {
if (factory == null)
return facade.logger = null;
else
return facade.logger = ((LoggerFactory) factory).getLogger(bundle, name, Logger.class);
}
synchronized LogDomain remove(Bundle bundle) {
return domains.remove(bundle);
}
synchronized void close() {
reset();
domains.clear();
}
}
final Lock lock = new Lock();
LogManager(BundleContext context) {
super(context, LOGGER_FACTORY_CLASS_NAME, null);
this.scrContext = context;
scrContext.addBundleListener(this);
}
@Override
public Object addingService(ServiceReference<Object> reference) {
Object service = super.addingService(reference);
Integer ranking = (Integer) reference.getProperty(Constants.SERVICE_RANKING);
if (ranking == null)
ranking = 0;
lock.setFactory(ranking, service);
return service;
}
@Override
public void removedService(ServiceReference<Object> reference, Object service) {
super.removedService(reference, service);
lock.removedFactory(service);
}
<T> T getLogger(Bundle bundle, String name, Class<T> type) {
return type.cast(lock.getLogDomain(bundle).getLogger(name));
}
@SuppressWarnings("resource")
@Override
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.STOPPED && !closed.get()) {
LogDomain domain = lock.remove(event.getBundle());
if (domain != null) {
domain.close();
}
}
}
/*
* Tracks a bundle's LoggerFactory service
*/
class LogDomain
implements Closeable {
private final Bundle bundle;
private final Set<LoggerFacade> facades = new HashSet<>();
LogDomain(Bundle bundle) {
this.bundle = bundle;
}
private void reset() {
synchronized (facades) {
for (LoggerFacade facade : facades) {
facade.reset();
}
}
}
LoggerFacade getLogger(String name) {
LoggerFacade facade = createLoggerFacade(this, name);
synchronized (facades) {
facades.add(facade);
}
return facade;
}
@Override
public void close() {
reset();
}
}
class LoggerFacade {
private final String name;
private final LogDomain domain;
volatile Object logger;
volatile String prefix;
LoggerFacade(LogDomain logDomain, String name) {
this.domain = logDomain;
this.name = name;
}
void reset() {
logger = null;
}
Object getLogger() {
Object l = this.logger;
if (l == null) {
l = lock.getLogger(this, domain.bundle, name);
}
return l;
}
Bundle getBundle() {
return domain.bundle;
}
String getName() {
return name;
}
}
public void close() {
if (closed.compareAndSet(false, true)) {
lock.close();
super.close();
this.context.removeBundleListener(this);
}
}
LoggerFacade createLoggerFacade(LogDomain logDomain, String name) {
assert !closed.get();
return new LoggerFacade(logDomain, name);
}
}