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.osgi2jul; 018 019import de.mnl.osgi.coreutils.ServiceResolver; 020import java.lang.reflect.InvocationTargetException; 021import java.security.AccessController; 022import java.security.PrivilegedActionException; 023import java.security.PrivilegedExceptionAction; 024import java.text.MessageFormat; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.List; 029import java.util.Optional; 030import java.util.concurrent.CountDownLatch; 031import java.util.logging.Handler; 032import java.util.logging.Level; 033import java.util.logging.LogRecord; 034import java.util.regex.Matcher; 035import java.util.regex.Pattern; 036import org.osgi.framework.Bundle; 037import org.osgi.framework.BundleContext; 038import org.osgi.service.log.LogEntry; 039import org.osgi.service.log.LogReaderService; 040 041/** 042 * This class provides the activator for this service. It registers 043 * (respectively unregisters) the {@link LogWriter} as LogListener 044 * for for all log reader services and forwards any already existing 045 * log entries to it. 046 */ 047@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 048public class ForwardingManager extends ServiceResolver { 049 050 private static final Pattern HANDLER_DEF = Pattern.compile( 051 "(?:(?<bundle>[^:]+):)?(?<class>[^\\[]+)(?:\\[(?<id>\\d+)\\])?"); 052 053 /** This tracker holds all log reader services. */ 054 private final List<HandlerConfig> handlers = new ArrayList<>(); 055 private LogReaderService subscribedService; 056 private LogWriter registeredListener; 057 058 /** 059 * Open the log service tracker. The tracker is customized to attach a 060 * {@link LogWriter} to all registered log reader services (and detach 061 * it on un-registration, of course). Already existing log entries 062 * are forwarded to the {@link LogWriter} as well. No provisions have been 063 * taken to avoid the duplicate output that can occur if a message 064 * is logged between registering the {@link LogWriter} and forwarding 065 * stored log entries. 066 */ 067 @Override 068 public void configure() { 069 createHandlers(context); 070 addDependency(LogReaderService.class); 071 } 072 073 /* 074 * (non-Javadoc) 075 * 076 * @see de.mnl.osgi.coreutils.ServiceResolver#onResolved() 077 */ 078 @Override 079 protected void onResolved() { 080 subscribeTo(get(LogReaderService.class)); 081 } 082 083 @Override 084 protected void onRebound(String dependency) { 085 if (LogReaderService.class.getName().equals(dependency)) { 086 subscribeTo(get(LogReaderService.class)); 087 } 088 } 089 090 @Override 091 protected void onDissolving() { 092 if (subscribedService != null && registeredListener != null) { 093 subscribedService.removeLogListener(registeredListener); 094 } 095 subscribedService = null; 096 } 097 098 private void subscribeTo(LogReaderService logReaderService) { 099 if (logReaderService.equals(subscribedService)) { 100 return; 101 } 102 if (subscribedService != null && registeredListener != null) { 103 subscribedService.removeLogListener(registeredListener); 104 } 105 subscribedService = logReaderService; 106 CountDownLatch enabled = new CountDownLatch(1); 107 registeredListener = new LogWriter(this, enabled); 108 subscribedService.addLogListener(registeredListener); 109 List<LogEntry> entries = Collections.list(subscribedService.getLog()); 110 Collections.reverse(entries); 111 LogWriter historyWriter = new LogWriter(this, new CountDownLatch(0)); 112 for (LogEntry entry : entries) { 113 historyWriter.logged(entry); 114 } 115 enabled.countDown(); 116 } 117 118 @SuppressWarnings({ "PMD.SystemPrintln", "PMD.DataflowAnomalyAnalysis" }) 119 private void createHandlers(BundleContext context) { 120 String handlerClasses = Optional.ofNullable( 121 context.getProperty( 122 ForwardingManager.class.getPackage().getName() + ".handlers")) 123 .orElse("java.util.logging.ConsoleHandler"); 124 Arrays.stream(handlerClasses.split(",")).map(String::trim) 125 .forEach(name -> { 126 Matcher parts = HANDLER_DEF.matcher(name); 127 if (!parts.matches()) { 128 System.err.println("Handler definition \"" 129 + name + "\" is invalid."); 130 return; 131 } 132 Handler handler; 133 try { 134 if (parts.group("bundle") == null) { 135 // Only class name 136 handler = handlerFromClassName(parts.group("class")); 137 } else { 138 handler = handlerFromBundledClass(context, 139 parts.group("bundle"), parts.group("class")); 140 } 141 } catch (Exception e) { // NOPMD 142 System.err.println("Can't load or configure log handler \"" 143 + name + "\": " + e.getMessage()); 144 return; 145 } 146 handlers.add(createHandlerConfig(context, parts, handler)); 147 }); 148 } 149 150 @SuppressWarnings("PMD.AvoidUncheckedExceptionsInSignatures") 151 private Handler handlerFromBundledClass(BundleContext context, 152 String bundleName, String className) 153 throws InstantiationException, IllegalAccessException, 154 ClassNotFoundException, IllegalArgumentException, 155 InvocationTargetException, NoSuchMethodException, 156 SecurityException { 157 for (Bundle bundle : context.getBundles()) { 158 if (bundle.getSymbolicName().equals(bundleName)) { 159 return (Handler) bundle.loadClass(className) 160 .getDeclaredConstructor().newInstance(); 161 } 162 } 163 throw new ClassNotFoundException("Class " + className + " not found " 164 + "in bundle " + bundleName); 165 } 166 167 private Handler handlerFromClassName(String name) 168 throws PrivilegedActionException { 169 Handler handler; 170 handler = AccessController 171 .doPrivileged(new PrivilegedExceptionAction<Handler>() { 172 @Override 173 public Handler run() throws Exception { 174 Class<?> hdlrCls; 175 hdlrCls = ClassLoader.getSystemClassLoader() 176 .loadClass(name); 177 return (Handler) hdlrCls.getDeclaredConstructor() 178 .newInstance(); 179 } 180 }); 181 return handler; 182 } 183 184 @SuppressWarnings("PMD.SystemPrintln") 185 private HandlerConfig createHandlerConfig(BundleContext context, 186 Matcher parts, Handler handler) { 187 String levelName = null; 188 MessageFormat outputFormat = null; 189 if (parts.group("id") != null) { 190 String handlerPrefix 191 = ForwardingManager.class.getPackage().getName() 192 + ".handler" + (parts.group("id") == null ? "" 193 : "." + parts.group("id")); 194 levelName = context.getProperty(handlerPrefix + ".level"); 195 String format = context.getProperty(handlerPrefix + ".format"); 196 if (format != null) { 197 try { 198 outputFormat = new MessageFormat(format); 199 } catch (IllegalArgumentException e) { 200 System.err.println("Illegal format: \"" + format + "\""); 201 } 202 } 203 } 204 if (levelName != null) { 205 Level level = Level.parse(levelName); 206 handler.setLevel(level); 207 } 208 return new HandlerConfig(handler, outputFormat); 209 } 210 211 @Override 212 public void stop(BundleContext context) throws Exception { 213 handlers.clear(); 214 super.stop(context); 215 } 216 217 /** 218 * Send the record to all handlers. 219 * 220 * @param entry the original OSGi log entry 221 * @param record the prepared JUL record 222 */ 223 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 224 public void publish(LogEntry entry, LogRecord record) { 225 Object[] xtraArgs = null; 226 for (HandlerConfig cfg : handlers) { 227 if (cfg.getOutputFormat() == null) { 228 cfg.getHandler().publish(record); 229 continue; 230 } 231 String oldMessage = record.getMessage(); 232 try { 233 if (xtraArgs == null) { 234 xtraArgs = new Object[] { 235 oldMessage, 236 entry.getBundle().getSymbolicName(), 237 entry.getBundle().getHeaders().get("Bundle-Name"), 238 entry.getBundle().getVersion().toString(), 239 entry.getThreadInfo() }; 240 } 241 record.setMessage(cfg.getOutputFormat().format(xtraArgs, 242 new StringBuffer(), null).toString()); 243 cfg.getHandler().publish(record); 244 } finally { 245 record.setMessage(oldMessage); 246 } 247 } 248 } 249 250}