View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.osgi;
18  
19  import java.lang.ref.WeakReference;
20  import java.net.URI;
21  import java.util.Objects;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  import org.apache.logging.log4j.core.LoggerContext;
26  import org.apache.logging.log4j.core.impl.ContextAnchor;
27  import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
28  import org.apache.logging.log4j.util.StackLocatorUtil;
29  import org.osgi.framework.Bundle;
30  import org.osgi.framework.BundleReference;
31  import org.osgi.framework.FrameworkUtil;
32  
33  /**
34   * ContextSelector for OSGi bundles. This ContextSelector works rather similarly to the
35   * {@link ClassLoaderContextSelector}, but instead of each ClassLoader having its own LoggerContext (like in a
36   * servlet container), each OSGi bundle has its own LoggerContext.
37   *
38   * @since 2.1
39   */
40  public class BundleContextSelector extends ClassLoaderContextSelector {
41  
42      @Override
43      public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
44                           final boolean allContexts) {
45          LoggerContext ctx = null;
46          Bundle bundle = null;
47          if (currentContext) {
48              ctx = ContextAnchor.THREAD_CONTEXT.get();
49              ContextAnchor.THREAD_CONTEXT.remove();
50          }
51          if (ctx == null && loader instanceof BundleReference) {
52              bundle = ((BundleReference) loader).getBundle();
53              ctx = getLoggerContext(bundle);
54              removeLoggerContext(ctx);
55          }
56          if (ctx == null) {
57              final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
58              if (callerClass != null) {
59                  bundle = FrameworkUtil.getBundle(callerClass);
60                  ctx = getLoggerContext(FrameworkUtil.getBundle(callerClass));
61                  removeLoggerContext(ctx);
62              }
63          }
64          if (ctx == null) {
65              ctx = ContextAnchor.THREAD_CONTEXT.get();
66              ContextAnchor.THREAD_CONTEXT.remove();
67          }
68          if (ctx != null) {
69              ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
70          }
71          if (bundle != null && allContexts) {
72              final Bundle[] bundles = bundle.getBundleContext().getBundles();
73              for (final Bundle bdl : bundles) {
74                  ctx = getLoggerContext(bdl);
75                  if (ctx != null) {
76                      ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
77                  }
78              }
79          }
80      }
81      private LoggerContext getLoggerContext(final Bundle bundle) {
82          final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
83          final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
84          if (ref != null && ref.get() != null) {
85              return ref.get().get();
86          }
87          return null;
88      }
89  
90      private void removeLoggerContext(LoggerContext context) {
91          CONTEXT_MAP.remove(context.getName());
92      }
93  
94      @Override
95      public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
96          if (currentContext && ContextAnchor.THREAD_CONTEXT.get() != null) {
97              return ContextAnchor.THREAD_CONTEXT.get().isStarted();
98          }
99          if (loader instanceof BundleReference) {
100             return hasContext(((BundleReference) loader).getBundle());
101         }
102         final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
103         if (callerClass != null) {
104             return hasContext(FrameworkUtil.getBundle(callerClass));
105         }
106         return ContextAnchor.THREAD_CONTEXT.get() != null && ContextAnchor.THREAD_CONTEXT.get().isStarted();
107     }
108     @Override
109     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
110                                     final URI configLocation) {
111         if (currentContext) {
112             final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
113             if (ctx != null) {
114                 return ctx;
115             }
116             return getDefault();
117         }
118         // it's quite possible that the provided ClassLoader may implement BundleReference which gives us a nice shortcut
119         if (loader instanceof BundleReference) {
120             return locateContext(((BundleReference) loader).getBundle(), configLocation);
121         }
122         final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
123         if (callerClass != null) {
124             return locateContext(FrameworkUtil.getBundle(callerClass), configLocation);
125         }
126         final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
127         return lc == null ? getDefault() : lc;
128     }
129 
130     private static boolean hasContext(final Bundle bundle) {
131         final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
132         final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
133         return ref != null && ref.get() != null && ref.get().get() != null && ref.get().get().isStarted();
134     }
135 
136     private static LoggerContext locateContext(final Bundle bundle, final URI configLocation) {
137         final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
138         final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
139         if (ref == null) {
140             final LoggerContext context = new LoggerContext(name, bundle, configLocation);
141             CONTEXT_MAP.putIfAbsent(name,
142                 new AtomicReference<>(new WeakReference<>(context)));
143             return CONTEXT_MAP.get(name).get().get();
144         }
145         final WeakReference<LoggerContext> r = ref.get();
146         final LoggerContext ctx = r.get();
147         if (ctx == null) {
148             final LoggerContext context = new LoggerContext(name, bundle, configLocation);
149             ref.compareAndSet(r, new WeakReference<>(context));
150             return ref.get().get();
151         }
152         final URI oldConfigLocation = ctx.getConfigLocation();
153         if (oldConfigLocation == null && configLocation != null) {
154             LOGGER.debug("Setting bundle ({}) configuration to {}", name, configLocation);
155             ctx.setConfigLocation(configLocation);
156         } else if (oldConfigLocation != null && configLocation != null && !configLocation.equals(oldConfigLocation)) {
157             LOGGER.warn("locateContext called with URI [{}], but existing LoggerContext has URI [{}]",
158                 configLocation, oldConfigLocation);
159         }
160         return ctx;
161     }
162 }