blob: 027f4a610a017751e76d26ba3272c55e9c86e512 [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
{
try
{
return facade.logger = ((LoggerFactory) factory).getLogger(bundle,
name, Logger.class);
}
catch (IllegalArgumentException e)
{
// The bundle probably got uninstalled or somehow unresolved,
// fallback to the SCR bundle's logger
return ((LoggerFactory) factory).getLogger(context.getBundle(), 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);
}
}