001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.config; 018 019import java.io.Serializable; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Array; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.concurrent.ConcurrentMap; 032import java.util.concurrent.CopyOnWriteArrayList; 033 034import org.apache.logging.log4j.Level; 035import org.apache.logging.log4j.LogManager; 036import org.apache.logging.log4j.Logger; 037import org.apache.logging.log4j.core.Appender; 038import org.apache.logging.log4j.core.Filter; 039import org.apache.logging.log4j.core.Layout; 040import org.apache.logging.log4j.core.LogEvent; 041import org.apache.logging.log4j.core.appender.AsyncAppender; 042import org.apache.logging.log4j.core.appender.ConsoleAppender; 043import org.apache.logging.log4j.core.async.AsyncLoggerConfig; 044import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; 045import org.apache.logging.log4j.core.config.plugins.PluginAliases; 046import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 047import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 048import org.apache.logging.log4j.core.config.plugins.PluginElement; 049import org.apache.logging.log4j.core.config.plugins.PluginFactory; 050import org.apache.logging.log4j.core.config.plugins.PluginManager; 051import org.apache.logging.log4j.core.config.plugins.PluginNode; 052import org.apache.logging.log4j.core.config.plugins.PluginType; 053import org.apache.logging.log4j.core.config.plugins.PluginValue; 054import org.apache.logging.log4j.core.filter.AbstractFilterable; 055import org.apache.logging.log4j.core.helpers.Constants; 056import org.apache.logging.log4j.core.helpers.NameUtil; 057import org.apache.logging.log4j.core.impl.Log4jContextFactory; 058import org.apache.logging.log4j.core.layout.PatternLayout; 059import org.apache.logging.log4j.core.lookup.Interpolator; 060import org.apache.logging.log4j.core.lookup.MapLookup; 061import org.apache.logging.log4j.core.lookup.StrLookup; 062import org.apache.logging.log4j.core.lookup.StrSubstitutor; 063import org.apache.logging.log4j.core.net.Advertiser; 064import org.apache.logging.log4j.core.selector.ContextSelector; 065import org.apache.logging.log4j.spi.LoggerContextFactory; 066import org.apache.logging.log4j.status.StatusLogger; 067import org.apache.logging.log4j.util.PropertiesUtil; 068 069/** 070 * The Base Configuration. Many configuration implementations will extend this class. 071 */ 072public class BaseConfiguration extends AbstractFilterable implements Configuration { 073 /** 074 * Allow subclasses access to the status logger without creating another instance. 075 */ 076 protected static final Logger LOGGER = StatusLogger.getLogger(); 077 078 /** 079 * The root node of the configuration. 080 */ 081 protected Node rootNode; 082 083 /** 084 * Listeners for configuration changes. 085 */ 086 protected final List<ConfigurationListener> listeners = 087 new CopyOnWriteArrayList<ConfigurationListener>(); 088 089 /** 090 * The ConfigurationMonitor that checks for configuration changes. 091 */ 092 protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor(); 093 094 /** 095 * The Advertiser which exposes appender configurations to external systems. 096 */ 097 private Advertiser advertiser = new DefaultAdvertiser(); 098 099 protected Map<String, String> advertisedConfiguration; 100 101 private Node advertiserNode = null; 102 103 private Object advertisement; 104 105 /** 106 * 107 */ 108 protected boolean isShutdownHookEnabled = true; 109 110 private String name; 111 112 private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<String, Appender>(); 113 114 private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>(); 115 116 private ConcurrentMap<String, String> properties = new ConcurrentHashMap<String, String>(); 117 118 private final StrLookup tempLookup = new Interpolator(properties); 119 120 private final StrSubstitutor subst = new StrSubstitutor(tempLookup); 121 122 private LoggerConfig root = new LoggerConfig(); 123 124 private final boolean started = false; 125 126 private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>(); 127 128 protected PluginManager pluginManager; 129 130 /** 131 * Constructor. 132 */ 133 protected BaseConfiguration() { 134 componentMap.put(Configuration.CONTEXT_PROPERTIES, properties); 135 pluginManager = new PluginManager("Core"); 136 rootNode = new Node(); 137 } 138 139 @Override 140 @SuppressWarnings("unchecked") 141 public Map<String, String> getProperties() { 142 return properties; 143 } 144 145 /** 146 * Initialize the configuration. 147 */ 148 @Override 149 public void start() { 150 pluginManager.collectPlugins(); 151 PluginManager levelPlugins = new PluginManager("Level"); 152 levelPlugins.collectPlugins(); 153 Map<String, PluginType<?>> plugins = levelPlugins.getPlugins(); 154 if (plugins != null) { 155 for (PluginType<?> type : plugins.values()) { 156 try { 157 // Cause the class to be initialized if it isn't already. 158 Class.forName(type.getPluginClass().getName(), true, type.getPluginClass().getClassLoader()); 159 } catch (Exception ex) { 160 LOGGER.error("Unable to initialize " + type.getPluginClass().getName() + " due to " + ex.getClass().getSimpleName() + ":" + ex.getMessage()); 161 } 162 } 163 } 164 setup(); 165 setupAdvertisement(); 166 doConfigure(); 167 for (final LoggerConfig logger : loggers.values()) { 168 logger.startFilter(); 169 } 170 for (final Appender appender : appenders.values()) { 171 appender.start(); 172 } 173 root.startFilter(); // LOG4J2-336 174 startFilter(); 175 } 176 177 /** 178 * Tear down the configuration. 179 */ 180 @Override 181 public void stop() { 182 183 // LOG4J2-392 first stop AsyncLogger Disruptor thread 184 final LoggerContextFactory factory = LogManager.getFactory(); 185 if (factory instanceof Log4jContextFactory) { 186 ContextSelector selector = ((Log4jContextFactory) factory).getSelector(); 187 if (selector instanceof AsyncLoggerContextSelector) { // all loggers are async 188 // TODO until LOG4J2-493 is fixed we can only stop AsyncLogger once! 189 // but LoggerContext.setConfiguration will call config.stop() 190 // every time the configuration changes... 191 // 192 // Uncomment the line below after LOG4J2-493 is fixed 193 //AsyncLogger.stop(); 194 } 195 } 196 // similarly, first stop AsyncLoggerConfig Disruptor thread(s) 197 Set<LoggerConfig> alreadyStopped = new HashSet<LoggerConfig>(); 198 for (final LoggerConfig logger : loggers.values()) { 199 if (logger instanceof AsyncLoggerConfig) { 200 logger.clearAppenders(); 201 logger.stopFilter(); 202 alreadyStopped.add(logger); 203 } 204 } 205 if (root instanceof AsyncLoggerConfig) { 206 root.stopFilter(); 207 alreadyStopped.add(root); 208 } 209 210 // Stop the appenders in reverse order in case they still have activity. 211 final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]); 212 213 // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first 214 for (int i = array.length - 1; i >= 0; --i) { 215 if (array[i] instanceof AsyncAppender) { 216 array[i].stop(); 217 } 218 } 219 for (int i = array.length - 1; i >= 0; --i) { 220 if (array[i].isStarted()) { // then stop remaining Appenders 221 array[i].stop(); 222 } 223 } 224 for (final LoggerConfig logger : loggers.values()) { 225 if (alreadyStopped.contains(logger)) { 226 continue; 227 } 228 logger.clearAppenders(); 229 logger.stopFilter(); 230 } 231 if (!alreadyStopped.contains(root)) { 232 root.stopFilter(); 233 } 234 stopFilter(); 235 if (advertiser != null && advertisement != null) { 236 advertiser.unadvertise(advertisement); 237 } 238 } 239 240 @Override 241 public boolean isShutdownHookEnabled() { 242 return isShutdownHookEnabled; 243 } 244 245 protected void setup() { 246 } 247 248 protected Level getDefaultStatus() { 249 final String statusLevel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL, 250 Level.ERROR.name()); 251 try { 252 return Level.toLevel(statusLevel); 253 } catch (final Exception ex) { 254 return Level.ERROR; 255 } 256 } 257 258 protected void createAdvertiser(String advertiserString, ConfigurationFactory.ConfigurationSource configSource, 259 byte[] buffer, String contentType) { 260 if (advertiserString != null) { 261 Node node = new Node(null, advertiserString, null); 262 Map<String, String> attributes = node.getAttributes(); 263 attributes.put("content", new String(buffer)); 264 attributes.put("contentType", contentType); 265 attributes.put("name", "configuration"); 266 if (configSource.getLocation() != null) { 267 attributes.put("location", configSource.getLocation()); 268 } 269 advertiserNode = node; 270 } 271 } 272 273 private void setupAdvertisement() { 274 if (advertiserNode != null) 275 { 276 String name = advertiserNode.getName(); 277 @SuppressWarnings("unchecked") 278 final PluginType<Advertiser> type = (PluginType<Advertiser>) pluginManager.getPluginType(name); 279 if (type != null) 280 { 281 final Class<Advertiser> clazz = type.getPluginClass(); 282 try { 283 advertiser = clazz.newInstance(); 284 advertisement = advertiser.advertise(advertiserNode.getAttributes()); 285 } catch (final InstantiationException e) { 286 System.err.println("InstantiationException attempting to instantiate advertiser: " + name); 287 } catch (final IllegalAccessException e) { 288 System.err.println("IllegalAccessException attempting to instantiate advertiser: " + name); 289 } 290 } 291 } 292 } 293 294 @SuppressWarnings("unchecked") 295 @Override 296 public <T> T getComponent(final String name) { 297 return (T) componentMap.get(name); 298 } 299 300 @Override 301 public void addComponent(final String name, final Object obj) { 302 componentMap.putIfAbsent(name, obj); 303 } 304 305 @SuppressWarnings("unchecked") 306 protected void doConfigure() { 307 boolean setRoot = false; 308 boolean setLoggers = false; 309 if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) { 310 Node first = rootNode.getChildren().get(0); 311 createConfiguration(first, null); 312 if (first.getObject() != null) { 313 subst.setVariableResolver((StrLookup) first.getObject()); 314 } 315 } else { 316 final Map<String, String> map = (Map<String, String>) componentMap.get(CONTEXT_PROPERTIES); 317 final StrLookup lookup = map == null ? null : new MapLookup(map); 318 subst.setVariableResolver(new Interpolator(lookup)); 319 } 320 321 for (final Node child : rootNode.getChildren()) { 322 if (child.getName().equalsIgnoreCase("Properties")) { 323 if (tempLookup == subst.getVariableResolver()) { 324 LOGGER.error("Properties declaration must be the first element in the configuration"); 325 } 326 continue; 327 } 328 createConfiguration(child, null); 329 if (child.getObject() == null) { 330 continue; 331 } 332 if (child.getName().equalsIgnoreCase("Appenders")) { 333 appenders = (ConcurrentMap<String, Appender>) child.getObject(); 334 } else if (child.getObject() instanceof Filter) { 335 addFilter((Filter) child.getObject()); 336 } else if (child.getName().equalsIgnoreCase("Loggers")) { 337 final Loggers l = (Loggers) child.getObject(); 338 loggers = l.getMap(); 339 setLoggers = true; 340 if (l.getRoot() != null) { 341 root = l.getRoot(); 342 setRoot = true; 343 } 344 } else { 345 LOGGER.error("Unknown object \"" + child.getName() + "\" of type " + 346 child.getObject().getClass().getName() + " is ignored"); 347 } 348 } 349 350 if (!setLoggers) { 351 LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?"); 352 setToDefault(); 353 return; 354 } else if (!setRoot) { 355 LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender"); 356 setToDefault(); 357 // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers 358 } 359 360 for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) { 361 final LoggerConfig l = entry.getValue(); 362 for (final AppenderRef ref : l.getAppenderRefs()) { 363 final Appender app = appenders.get(ref.getRef()); 364 if (app != null) { 365 l.addAppender(app, ref.getLevel(), ref.getFilter()); 366 } else { 367 LOGGER.error("Unable to locate appender " + ref.getRef() + " for logger " + l.getName()); 368 } 369 } 370 371 } 372 373 setParents(); 374 } 375 376 private void setToDefault() { 377 setName(DefaultConfiguration.DEFAULT_NAME); 378 final Layout<? extends Serializable> layout = 379 PatternLayout.createLayout("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n", 380 null, null, null, null, null); 381 final Appender appender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "Console", "false", 382 "true"); 383 appender.start(); 384 addAppender(appender); 385 final LoggerConfig root = getRootLogger(); 386 root.addAppender(appender, null, null); 387 388 final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL); 389 final Level level = levelName != null && Level.getLevel(levelName) != null ? 390 Level.getLevel(levelName) : Level.ERROR; 391 root.setLevel(level); 392 } 393 394 /** 395 * Set the name of the configuration. 396 * @param name The name. 397 */ 398 public void setName(final String name) { 399 this.name = name; 400 } 401 402 /** 403 * Returns the name of the configuration. 404 * @return the name of the configuration. 405 */ 406 @Override 407 public String getName() { 408 return name; 409 } 410 411 /** 412 * Add a listener for changes on the configuration. 413 * @param listener The ConfigurationListener to add. 414 */ 415 @Override 416 public void addListener(final ConfigurationListener listener) { 417 listeners.add(listener); 418 } 419 420 /** 421 * Remove a ConfigurationListener. 422 * @param listener The ConfigurationListener to remove. 423 */ 424 @Override 425 public void removeListener(final ConfigurationListener listener) { 426 listeners.remove(listener); 427 } 428 429 /** 430 * Returns the Appender with the specified name. 431 * @param name The name of the Appender. 432 * @return the Appender with the specified name or null if the Appender cannot be located. 433 */ 434 public Appender getAppender(final String name) { 435 return appenders.get(name); 436 } 437 438 /** 439 * Returns a Map containing all the Appenders and their name. 440 * @return A Map containing each Appender's name and the Appender object. 441 */ 442 @Override 443 public Map<String, Appender> getAppenders() { 444 return appenders; 445 } 446 447 /** 448 * Adds an Appender to the configuration. 449 * @param appender The Appender to add. 450 */ 451 public void addAppender(final Appender appender) { 452 appenders.put(appender.getName(), appender); 453 } 454 455 @Override 456 public StrSubstitutor getStrSubstitutor() { 457 return subst; 458 } 459 460 @Override 461 public void setConfigurationMonitor(final ConfigurationMonitor monitor) { 462 this.monitor = monitor; 463 } 464 465 @Override 466 public ConfigurationMonitor getConfigurationMonitor() { 467 return monitor; 468 } 469 470 @Override 471 public void setAdvertiser(final Advertiser advertiser) { 472 this.advertiser = advertiser; 473 } 474 475 @Override 476 public Advertiser getAdvertiser() { 477 return advertiser; 478 } 479 480 /** 481 * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the 482 * same name is being updated at the same time. 483 * 484 * Note: This method is not used when configuring via configuration. It is primarily used by 485 * unit tests. 486 * @param logger The Logger the Appender will be associated with. 487 * @param appender The Appender. 488 */ 489 @Override 490 public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger, 491 final Appender appender) { 492 final String name = logger.getName(); 493 appenders.putIfAbsent(appender.getName(), appender); 494 final LoggerConfig lc = getLoggerConfig(name); 495 if (lc.getName().equals(name)) { 496 lc.addAppender(appender, null, null); 497 } else { 498 final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive()); 499 nlc.addAppender(appender, null, null); 500 nlc.setParent(lc); 501 loggers.putIfAbsent(name, nlc); 502 setParents(); 503 logger.getContext().updateLoggers(); 504 } 505 } 506 /** 507 * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the 508 * same name is being updated at the same time. 509 * 510 * Note: This method is not used when configuring via configuration. It is primarily used by 511 * unit tests. 512 * @param logger The Logger the Fo;ter will be associated with. 513 * @param filter The Filter. 514 */ 515 @Override 516 public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) { 517 final String name = logger.getName(); 518 final LoggerConfig lc = getLoggerConfig(name); 519 if (lc.getName().equals(name)) { 520 521 lc.addFilter(filter); 522 } else { 523 final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive()); 524 nlc.addFilter(filter); 525 nlc.setParent(lc); 526 loggers.putIfAbsent(name, nlc); 527 setParents(); 528 logger.getContext().updateLoggers(); 529 } 530 } 531 /** 532 * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the 533 * same name is being updated at the same time. 534 * 535 * Note: This method is not used when configuring via configuration. It is primarily used by 536 * unit tests. 537 * @param logger The Logger the Appender will be associated with. 538 * @param additive True if the LoggerConfig should be additive, false otherwise. 539 */ 540 @Override 541 public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, 542 final boolean additive) { 543 final String name = logger.getName(); 544 final LoggerConfig lc = getLoggerConfig(name); 545 if (lc.getName().equals(name)) { 546 lc.setAdditive(additive); 547 } else { 548 final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), additive); 549 nlc.setParent(lc); 550 loggers.putIfAbsent(name, nlc); 551 setParents(); 552 logger.getContext().updateLoggers(); 553 } 554 } 555 556 /** 557 * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes 558 * the Appender from this appender list and then stops the appender. This method is synchronized in 559 * case an Appender with the same name is being added during the removal. 560 * @param name the name of the appender to remove. 561 */ 562 public synchronized void removeAppender(final String name) { 563 for (final LoggerConfig logger : loggers.values()) { 564 logger.removeAppender(name); 565 } 566 final Appender app = appenders.remove(name); 567 568 if (app != null) { 569 app.stop(); 570 } 571 } 572 573 /** 574 * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the 575 * package name as necessary or return the root LoggerConfig if no other matches were found. 576 * @param name The Logger name. 577 * @return The located LoggerConfig. 578 */ 579 @Override 580 public LoggerConfig getLoggerConfig(final String name) { 581 if (loggers.containsKey(name)) { 582 return loggers.get(name); 583 } 584 String substr = name; 585 while ((substr = NameUtil.getSubName(substr)) != null) { 586 if (loggers.containsKey(substr)) { 587 return loggers.get(substr); 588 } 589 } 590 return root; 591 } 592 593 /** 594 * Returns the root Logger. 595 * @return the root Logger. 596 */ 597 public LoggerConfig getRootLogger() { 598 return root; 599 } 600 601 /** 602 * Returns a Map of all the LoggerConfigs. 603 * @return a Map with each entry containing the name of the Logger and the LoggerConfig. 604 */ 605 @Override 606 public Map<String, LoggerConfig> getLoggers() { 607 return Collections.unmodifiableMap(loggers); 608 } 609 610 /** 611 * Returns the LoggerConfig with the specified name. 612 * @param name The Logger name. 613 * @return The LoggerConfig or null if no match was found. 614 */ 615 public LoggerConfig getLogger(final String name) { 616 return loggers.get(name); 617 } 618 619 /** 620 * Adding a logger cannot be done atomically so is not allowed in an active configuration. Adding 621 * or removing a Logger requires creating a new configuration and then switching. 622 * 623 * @param name The name of the Logger. 624 * @param loggerConfig The LoggerConfig. 625 */ 626 public void addLogger(final String name, final LoggerConfig loggerConfig) { 627 if (started) { 628 final String msg = "Cannot add logger " + name + " to an active configuration"; 629 LOGGER.warn(msg); 630 throw new IllegalStateException(msg); 631 } 632 loggers.put(name, loggerConfig); 633 setParents(); 634 } 635 636 /** 637 * Removing a logger cannot be done atomically so is not allowed in an active configuration. Adding 638 * or removing a Logger requires creating a new configuration and then switching. 639 * 640 * @param name The name of the Logger. 641 */ 642 public void removeLogger(final String name) { 643 if (started) { 644 final String msg = "Cannot remove logger " + name + " in an active configuration"; 645 LOGGER.warn(msg); 646 throw new IllegalStateException(msg); 647 } 648 loggers.remove(name); 649 setParents(); 650 } 651 652 @Override 653 public void createConfiguration(final Node node, final LogEvent event) { 654 final PluginType<?> type = node.getType(); 655 if (type != null && type.isDeferChildren()) { 656 node.setObject(createPluginObject(type, node, event)); 657 } else { 658 for (final Node child : node.getChildren()) { 659 createConfiguration(child, event); 660 } 661 662 if (type == null) { 663 if (node.getParent() != null) { 664 LOGGER.error("Unable to locate plugin for " + node.getName()); 665 } 666 } else { 667 node.setObject(createPluginObject(type, node, event)); 668 } 669 } 670 } 671 672 /* 673 * Retrieve a static public 'method to create the desired object. Every parameter 674 * will be annotated to identify the appropriate attribute or element to use to 675 * set the value of the parameter. 676 * Parameters annotated with PluginAttribute will always be set as Strings. 677 * Parameters annotated with PluginElement may be Objects or arrays. Collections 678 * and Maps are currently not supported, although the factory method that is called 679 * can create these from an array. 680 * 681 * Although the happy path works, more work still needs to be done to log incorrect 682 * parameters. These will generally result in unhelpful InvocationTargetExceptions. 683 * @param classClass the class. 684 * @return the instantiate method or null if there is none by that 685 * description. 686 */ 687 private <T> Object createPluginObject(final PluginType<T> type, final Node node, final LogEvent event) 688 { 689 final Class<T> clazz = type.getPluginClass(); 690 691 if (Map.class.isAssignableFrom(clazz)) { 692 try { 693 @SuppressWarnings("unchecked") 694 final Map<String, Object> map = (Map<String, Object>) clazz.newInstance(); 695 for (final Node child : node.getChildren()) { 696 map.put(child.getName(), child.getObject()); 697 } 698 return map; 699 } catch (final Exception ex) { 700 LOGGER.warn("Unable to create Map for " + type.getElementName() + " of class " + 701 clazz); 702 } 703 } 704 705 if (List.class.isAssignableFrom(clazz)) { 706 try { 707 @SuppressWarnings("unchecked") 708 final List<Object> list = (List<Object>) clazz.newInstance(); 709 for (final Node child : node.getChildren()) { 710 list.add(child.getObject()); 711 } 712 return list; 713 } catch (final Exception ex) { 714 LOGGER.warn("Unable to create List for " + type.getElementName() + " of class " + 715 clazz); 716 } 717 } 718 719 Method factoryMethod = null; 720 721 for (final Method method : clazz.getMethods()) { 722 if (method.isAnnotationPresent(PluginFactory.class)) { 723 factoryMethod = method; 724 break; 725 } 726 } 727 if (factoryMethod == null) { 728 return null; 729 } 730 731 final Annotation[][] parmArray = factoryMethod.getParameterAnnotations(); 732 final Class<?>[] parmClasses = factoryMethod.getParameterTypes(); 733 if (parmArray.length != parmClasses.length) { 734 LOGGER.error("Number of parameter annotations does not equal the number of paramters"); 735 } 736 final Object[] parms = new Object[parmClasses.length]; 737 738 int index = 0; 739 final Map<String, String> attrs = node.getAttributes(); 740 final List<Node> children = node.getChildren(); 741 final StringBuilder sb = new StringBuilder(); 742 final List<Node> used = new ArrayList<Node>(); 743 744 /* 745 * For each parameter: 746 * If the parameter is an attribute store the value of the attribute in the parameter array. 747 * If the parameter is an element: 748 * Determine if the required parameter is an array. 749 * If so, if a child contains the array, use it, 750 * otherwise create the array from all child nodes of the correct type. 751 * Store the array into the parameter array. 752 * If not an array, store the object in the child node into the parameter array. 753 */ 754 for (final Annotation[] parmTypes : parmArray) { 755 String[] aliases = null; 756 for (final Annotation a: parmTypes) { 757 if (a instanceof PluginAliases) { 758 aliases = ((PluginAliases) a).value(); 759 } 760 } 761 for (final Annotation a : parmTypes) { 762 if (a instanceof PluginAliases) { 763 continue; 764 } 765 if (sb.length() == 0) { 766 sb.append(" with params("); 767 } else { 768 sb.append(", "); 769 } 770 if (a instanceof PluginNode) { 771 parms[index] = node; 772 sb.append("Node=").append(node.getName()); 773 } else if (a instanceof PluginConfiguration) { 774 parms[index] = this; 775 if (this.name != null) { 776 sb.append("Configuration(").append(name).append(")"); 777 } else { 778 sb.append("Configuration"); 779 } 780 } else if (a instanceof PluginValue) { 781 final String name = ((PluginValue) a).value(); 782 String v = node.getValue(); 783 if (v == null) { 784 v = getAttrValue("value", null, attrs); 785 } 786 final String value = subst.replace(event, v); 787 sb.append(name).append("=\"").append(value).append("\""); 788 parms[index] = value; 789 } else if (a instanceof PluginAttribute) { 790 PluginAttribute attr = (PluginAttribute) a; 791 final String name = attr.value(); 792 final String value = subst.replace(event, getAttrValue(name, aliases, attrs)); 793 sb.append(name).append("=\"").append(value).append("\""); 794 parms[index] = value; 795 } else if (a instanceof PluginElement) { 796 final PluginElement elem = (PluginElement) a; 797 final String name = elem.value(); 798 if (parmClasses[index].isArray()) { 799 final Class<?> parmClass = parmClasses[index].getComponentType(); 800 final List<Object> list = new ArrayList<Object>(); 801 sb.append(name).append("={"); 802 boolean first = true; 803 for (final Node child : children) { 804 final PluginType<?> childType = child.getType(); 805 if (elem.value().equalsIgnoreCase(childType.getElementName()) || 806 parmClass.isAssignableFrom(childType.getPluginClass())) { 807 used.add(child); 808 if (!first) { 809 sb.append(", "); 810 } 811 first = false; 812 final Object obj = child.getObject(); 813 if (obj == null) { 814 LOGGER.error("Null object returned for " + child.getName() + " in " + 815 node.getName()); 816 continue; 817 } 818 if (obj.getClass().isArray()) { 819 printArray(sb, (Object[]) obj); 820 parms[index] = obj; 821 break; 822 } 823 sb.append(child.toString()); 824 list.add(obj); 825 } 826 } 827 sb.append("}"); 828 if (parms[index] != null) { 829 break; 830 } 831 if (list.size() > 0 && !parmClass.isAssignableFrom(list.get(0).getClass())) { 832 LOGGER.error("Attempted to assign List containing class " + 833 list.get(0).getClass().getName() + " to array of type " + parmClass + 834 " for attribute " + name); 835 break; 836 } 837 final Object[] array = (Object[]) Array.newInstance(parmClass, list.size()); 838 int i = 0; 839 for (final Object obj : list) { 840 array[i] = obj; 841 ++i; 842 } 843 parms[index] = array; 844 } else { 845 final Class<?> parmClass = parmClasses[index]; 846 boolean present = false; 847 for (final Node child : children) { 848 final PluginType<?> childType = child.getType(); 849 if (elem.value().equals(childType.getElementName()) || 850 parmClass.isAssignableFrom(childType.getPluginClass())) { 851 sb.append(child.getName()).append("(").append(child.toString()).append(")"); 852 present = true; 853 used.add(child); 854 parms[index] = child.getObject(); 855 break; 856 } 857 } 858 if (!present) { 859 sb.append("null"); 860 } 861 } 862 } 863 } 864 ++index; 865 } 866 if (sb.length() > 0) { 867 sb.append(")"); 868 } 869 870 if (attrs.size() > 0) { 871 final StringBuilder eb = new StringBuilder(); 872 for (final String key : attrs.keySet()) { 873 if (eb.length() == 0) { 874 eb.append(node.getName()); 875 eb.append(" contains "); 876 if (attrs.size() == 1) { 877 eb.append("an invalid element or attribute "); 878 } else { 879 eb.append("invalid attributes "); 880 } 881 } else { 882 eb.append(", "); 883 } 884 eb.append("\""); 885 eb.append(key); 886 eb.append("\""); 887 888 } 889 LOGGER.error(eb.toString()); 890 } 891 892 if (!type.isDeferChildren() && used.size() != children.size()) { 893 for (final Node child : children) { 894 if (used.contains(child)) { 895 continue; 896 } 897 final String nodeType = node.getType().getElementName(); 898 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + " " + node.getName(); 899 LOGGER.error(start + " has no parameter that matches element " + child.getName()); 900 } 901 } 902 903 try { 904 final int mod = factoryMethod.getModifiers(); 905 if (!Modifier.isStatic(mod)) { 906 LOGGER.error(factoryMethod.getName() + " method is not static on class " + 907 clazz.getName() + " for element " + node.getName()); 908 return null; 909 } 910 LOGGER.debug("Calling {} on class {} for element {}{}", factoryMethod.getName(), clazz.getName(), 911 node.getName(), sb.toString()); 912 //if (parms.length > 0) { 913 return factoryMethod.invoke(null, parms); 914 //} 915 //return factoryMethod.invoke(null, node); 916 } catch (final Exception e) { 917 LOGGER.error("Unable to invoke method " + factoryMethod.getName() + " in class " + 918 clazz.getName() + " for element " + node.getName(), e); 919 } 920 return null; 921 } 922 923 private void printArray(final StringBuilder sb, final Object... array) { 924 boolean first = true; 925 for (final Object obj : array) { 926 if (!first) { 927 sb.append(", "); 928 } 929 sb.append(obj.toString()); 930 first = false; 931 } 932 } 933 934 private String getAttrValue(final String name, final String[] aliases, final Map<String, String> attrs) { 935 for (final String key : attrs.keySet()) { 936 if (key.equalsIgnoreCase(name)) { 937 final String attr = attrs.get(key); 938 attrs.remove(key); 939 return attr; 940 } 941 if (aliases != null) { 942 for (String alias : aliases) { 943 if (key.equalsIgnoreCase(alias)) { 944 final String attr = attrs.get(key); 945 attrs.remove(key); 946 return attr; 947 } 948 } 949 } 950 } 951 return null; 952 } 953 954 private void setParents() { 955 for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) { 956 final LoggerConfig logger = entry.getValue(); 957 String name = entry.getKey(); 958 if (!name.equals("")) { 959 final int i = name.lastIndexOf('.'); 960 if (i > 0) { 961 name = name.substring(0, i); 962 LoggerConfig parent = getLoggerConfig(name); 963 if (parent == null) { 964 parent = root; 965 } 966 logger.setParent(parent); 967 } else { 968 logger.setParent(root); 969 } 970 } 971 } 972 } 973}