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 * https://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
18 package org.apache.commons.logging.impl;
19
20 import java.io.PrintWriter;
21 import java.io.StringWriter;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.net.URL;
26 import java.security.AccessController;
27 import java.security.PrivilegedAction;
28 import java.util.Hashtable;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogConfigurationException;
32 import org.apache.commons.logging.LogFactory;
33
34 /**
35 * Concrete subclass of {@link LogFactory} that implements the
36 * following algorithm to dynamically select a logging implementation
37 * class to instantiate a wrapper for:
38 * <ul>
39 * <li>Use a factory configuration attribute named
40 * {@code org.apache.commons.logging.Log} to identify the
41 * requested implementation class.</li>
42 * <li>Use the {@code org.apache.commons.logging.Log} system property
43 * to identify the requested implementation class.</li>
44 * <li>If <em>Log4J</em> is available, return an instance of
45 * {@code org.apache.commons.logging.impl.Log4JLogger}.</li>
46 * <li>If <em>JDK 1.4 or later</em> is available, return an instance of
47 * {@code org.apache.commons.logging.impl.Jdk14Logger}.</li>
48 * <li>Otherwise, return an instance of
49 * {@code org.apache.commons.logging.impl.SimpleLog}.</li>
50 * </ul>
51 * <p>
52 * If the selected {@link Log} implementation class has a
53 * {@code setLogFactory()} method that accepts a {@link LogFactory}
54 * parameter, this method will be called on each newly created instance
55 * to identify the associated factory. This makes factory configuration
56 * attributes available to the Log instance, if it so desires.
57 * </p>
58 * <p>
59 * This factory will remember previously created {@code Log} instances
60 * for the same name, and will return them on repeated requests to the
61 * {@code getInstance()} method.
62 * </p>
63 */
64 public class LogFactoryImpl extends LogFactory {
65
66 /** Log4JLogger class name */
67 private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
68
69 /** Jdk14Logger class name */
70 private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger";
71
72 /** Jdk13LumberjackLogger class name */
73 private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger";
74
75 /** SimpleLog class name */
76 private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog";
77
78 private static final String PKG_IMPL="org.apache.commons.logging.impl.";
79
80 private static final int PKG_LEN = PKG_IMPL.length();
81
82 /**
83 * An empty immutable {@code String} array.
84 */
85 private static final String[] EMPTY_STRING_ARRAY = {};
86
87 /**
88 * The name ({@code org.apache.commons.logging.Log}) of the system
89 * property identifying our {@link Log} implementation class.
90 */
91 public static final String LOG_PROPERTY = "org.apache.commons.logging.Log";
92
93 /**
94 * The deprecated system property used for backwards compatibility with
95 * old versions of JCL.
96 */
97 protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log";
98
99 /**
100 * The name ({@code org.apache.commons.logging.Log.allowFlawedContext})
101 * of the system property which can be set true/false to
102 * determine system behavior when a bad context class loader is encountered.
103 * When set to false, a LogConfigurationException is thrown if
104 * LogFactoryImpl is loaded via a child class loader of the TCCL (this
105 * should never happen in sane systems).
106 * <p>
107 * Default behavior: true (tolerates bad context class loaders)
108 * </p>
109 * <p>
110 * See also method setAttribute.
111 * </p>
112 */
113 public static final String ALLOW_FLAWED_CONTEXT_PROPERTY =
114 "org.apache.commons.logging.Log.allowFlawedContext";
115
116 /**
117 * The name ({@code org.apache.commons.logging.Log.allowFlawedDiscovery})
118 * of the system property which can be set true/false to
119 * determine system behavior when a bad logging adapter class is
120 * encountered during logging discovery. When set to false, an
121 * exception will be thrown and the app will fail to start. When set
122 * to true, discovery will continue (though the user might end up
123 * with a different logging implementation than they expected).
124 * <p>
125 * Default behavior: true (tolerates bad logging adapters)
126 * </p>
127 * <p>
128 * See also method setAttribute.
129 * </p>
130 */
131 public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY =
132 "org.apache.commons.logging.Log.allowFlawedDiscovery";
133
134 /**
135 * The name ({@code org.apache.commons.logging.Log.allowFlawedHierarchy})
136 * of the system property which can be set true/false to
137 * determine system behavior when a logging adapter class is
138 * encountered which has bound to the wrong Log class implementation.
139 * When set to false, an exception will be thrown and the app will fail
140 * to start. When set to true, discovery will continue (though the user
141 * might end up with a different logging implementation than they expected).
142 * <p>
143 * Default behavior: true (tolerates bad Log class hierarchy)
144 * </p>
145 * <p>
146 * See also method setAttribute.
147 * </p>
148 */
149 public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY =
150 "org.apache.commons.logging.Log.allowFlawedHierarchy";
151
152 /**
153 * The names of classes that will be tried (in order) as logging
154 * adapters. Each class is expected to implement the Log interface,
155 * and to throw NoClassDefFound or ExceptionInInitializerError when
156 * loaded if the underlying logging library is not available. Any
157 * other error indicates that the underlying logging library is available
158 * but broken/unusable for some reason.
159 */
160 private static final String[] DISCOVER_CLASSES = {
161 LOGGING_IMPL_JDK14_LOGGER,
162 LOGGING_IMPL_SIMPLE_LOGGER
163 };
164
165 /**
166 * Workaround for bug in Java1.2; in theory this method is not needed. {@link LogFactory#getClassLoader(Class)}.
167 *
168 * @param clazz See {@link LogFactory#getClassLoader(Class)}.
169 * @return See {@link LogFactory#getClassLoader(Class)}.
170 * @since 1.1
171 */
172 protected static ClassLoader getClassLoader(final Class<?> clazz) {
173 return LogFactory.getClassLoader(clazz);
174 }
175
176 /**
177 * Gets the context ClassLoader.
178 * This method is a workaround for a Java 1.2 compiler bug.
179 *
180 * @return the context ClassLoader
181 * @since 1.1
182 */
183 protected static ClassLoader getContextClassLoader() throws LogConfigurationException {
184 return LogFactory.getContextClassLoader();
185 }
186
187 /**
188 * Calls LogFactory.directGetContextClassLoader under the control of an
189 * AccessController class. This means that Java code running under a
190 * security manager that forbids access to ClassLoaders will still work
191 * if this class is given appropriate privileges, even when the caller
192 * doesn't have such privileges. Without using an AccessController, the
193 * the entire call stack must have the privilege before the call is
194 * allowed.
195 *
196 * @return the context class loader associated with the current thread,
197 * or null if security doesn't allow it.
198 *
199 * @throws LogConfigurationException if there was some weird error while
200 * attempting to get the context class loader.
201 *
202 * @throws SecurityException if the current Java security policy doesn't
203 * allow this class to access the context class loader.
204 */
205 private static ClassLoader getContextClassLoaderInternal()
206 throws LogConfigurationException {
207 return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) LogFactory::directGetContextClassLoader);
208 }
209
210 /**
211 * Reads the specified system property, using an AccessController so that
212 * the property can be read if JCL has been granted the appropriate
213 * security rights even if the calling code has not.
214 * <p>
215 * Take care not to expose the value returned by this method to the
216 * calling application in any way; otherwise the calling app can use that
217 * info to access data that should not be available to it.
218 * </p>
219 */
220 private static String getSystemProperty(final String key, final String def)
221 throws SecurityException {
222 return AccessController.doPrivileged((PrivilegedAction<String>) () -> System.getProperty(key, def));
223 }
224
225 /**
226 * Workaround for bug in Java1.2; in theory this method is not needed.
227 *
228 * @return Same as {@link LogFactory#isDiagnosticsEnabled()}.
229 * @see LogFactory#isDiagnosticsEnabled()
230 */
231 protected static boolean isDiagnosticsEnabled() {
232 return LogFactory.isDiagnosticsEnabled();
233 }
234
235 /** Utility method to safely trim a string. */
236 private static String trim(final String src) {
237 if (src == null) {
238 return null;
239 }
240 return src.trim();
241 }
242
243 /**
244 * Determines whether logging classes should be loaded using the thread-context
245 * class loader, or via the class loader that loaded this LogFactoryImpl class.
246 */
247 private boolean useTCCL = true;
248
249 /**
250 * The string prefixed to every message output by the logDiagnostic method.
251 */
252 private String diagnosticPrefix;
253
254 /**
255 * Configuration attributes.
256 */
257 protected Hashtable<String, Object> attributes = new Hashtable<>();
258
259 /**
260 * The {@link org.apache.commons.logging.Log} instances that have
261 * already been created, keyed by logger name.
262 */
263 protected Hashtable<String, Log> instances = new Hashtable<>();
264
265 /**
266 * Name of the class implementing the Log interface.
267 */
268 private String logClassName;
269
270 /**
271 * The one-argument constructor of the
272 * {@link org.apache.commons.logging.Log}
273 * implementation class that will be used to create new instances.
274 * This value is initialized by {@code getLogConstructor()},
275 * and then returned repeatedly.
276 */
277 protected Constructor<?> logConstructor;
278
279 /**
280 * The signature of the Constructor to be used.
281 */
282 protected Class<?>[] logConstructorSignature = { String.class };
283
284 /**
285 * The one-argument {@code setLogFactory} method of the selected
286 * {@link org.apache.commons.logging.Log} method, if it exists.
287 */
288 protected Method logMethod;
289
290 /**
291 * The signature of the {@code setLogFactory} method to be used.
292 */
293 protected Class<?>[] logMethodSignature = { LogFactory.class };
294
295 /**
296 * See getBaseClassLoader and initConfiguration.
297 */
298 private boolean allowFlawedContext;
299
300 /**
301 * See handleFlawedDiscovery and initConfiguration.
302 */
303 private boolean allowFlawedDiscovery;
304
305 /**
306 * See handleFlawedHierarchy and initConfiguration.
307 */
308 private boolean allowFlawedHierarchy;
309
310 /**
311 * Public no-arguments constructor required by the lookup mechanism.
312 */
313 public LogFactoryImpl() {
314 initDiagnostics(); // method on this object
315 if (isDiagnosticsEnabled()) {
316 logDiagnostic("Instance created.");
317 }
318 }
319
320 /**
321 * Attempts to load the given class, find a suitable constructor,
322 * and instantiate an instance of Log.
323 *
324 * @param logAdapterClassName class name of the Log implementation
325 * @param logCategory argument to pass to the Log implementation's constructor
326 * @param affectState {@code true} if this object's state should
327 * be affected by this method call, {@code false} otherwise.
328 * @return an instance of the given class, or null if the logging
329 * library associated with the specified adapter is not available.
330 * @throws LogConfigurationException if there was a serious error with
331 * configuration and the handleFlawedDiscovery method decided this
332 * problem was fatal.
333 */
334 private Log createLogFromClass(final String logAdapterClassName, final String logCategory, final boolean affectState) throws LogConfigurationException {
335 if (isDiagnosticsEnabled()) {
336 logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'");
337 }
338 final Object[] params = { logCategory };
339 Log logAdapter = null;
340 Constructor<?> constructor = null;
341 Class<?> logAdapterClass = null;
342 ClassLoader currentCL = getBaseClassLoader();
343 for (;;) {
344 // Loop through the class loader hierarchy trying to find
345 // a viable class loader.
346 logDiagnostic("Trying to load '" + logAdapterClassName + "' from class loader " + objectId(currentCL));
347 try {
348 if (isDiagnosticsEnabled()) {
349 // Show the location of the first occurrence of the .class file
350 // in the classpath. This is the location that ClassLoader.loadClass
351 // will load the class from -- unless the class loader is doing
352 // something weird.
353 URL url;
354 final String resourceName = logAdapterClassName.replace('.', '/') + ".class";
355 if (currentCL != null) {
356 url = currentCL.getResource(resourceName);
357 } else {
358 url = ClassLoader.getSystemResource(resourceName + ".class");
359 }
360 if (url == null) {
361 logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found.");
362 } else {
363 logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'");
364 }
365 }
366 Class<?> clazz;
367 try {
368 clazz = Class.forName(logAdapterClassName, true, currentCL);
369 } catch (final ClassNotFoundException originalClassNotFoundException) {
370 // The current class loader was unable to find the log adapter
371 // in this or any ancestor class loader. There's no point in
372 // trying higher up in the hierarchy in this case.
373 String msg = originalClassNotFoundException.getMessage();
374 logDiagnostic("The log adapter '" + logAdapterClassName + "' is not available via class loader " + objectId(currentCL) + ": " + trim(msg));
375 try {
376 // Try the class class loader.
377 // This may work in cases where the TCCL
378 // does not contain the code executed or JCL.
379 // This behavior indicates that the application
380 // classloading strategy is not consistent with the
381 // Java 1.2 classloading guidelines but JCL can
382 // and so should handle this case.
383 clazz = Class.forName(logAdapterClassName);
384 } catch (final ClassNotFoundException secondaryClassNotFoundException) {
385 // no point continuing: this adapter isn't available
386 msg = secondaryClassNotFoundException.getMessage();
387 logDiagnostic("The log adapter '" + logAdapterClassName + "' is not available via the LogFactoryImpl class class loader: " + trim(msg));
388 break;
389 }
390 }
391 constructor = clazz.getConstructor(logConstructorSignature);
392 final Object o = constructor.newInstance(params);
393 // Note that we do this test after trying to create an instance
394 // [rather than testing Log.class.isAssignableFrom(c)] so that
395 // we don't complain about Log hierarchy problems when the
396 // adapter couldn't be instantiated anyway.
397 if (o instanceof Log) {
398 logAdapterClass = clazz;
399 logAdapter = (Log) o;
400 break;
401 }
402 // Oops, we have a potential problem here. An adapter class
403 // has been found and its underlying lib is present too, but
404 // there are multiple Log interface classes available making it
405 // impossible to cast to the type the caller wanted. We
406 // certainly can't use this logger, but we need to know whether
407 // to keep on discovering or terminate now.
408 //
409 // The handleFlawedHierarchy method will throw
410 // LogConfigurationException if it regards this problem as
411 // fatal, and just return if not.
412 handleFlawedHierarchy(currentCL, clazz);
413 } catch (final NoClassDefFoundError e) {
414 // We were able to load the adapter but it had references to
415 // other classes that could not be found. This simply means that
416 // the underlying logger library is not present in this or any
417 // ancestor class loader. There's no point in trying higher up
418 // in the hierarchy in this case.
419 final String msg = e.getMessage();
420 logDiagnostic("The log adapter '" + logAdapterClassName + "' is missing dependencies when loaded via class loader " + objectId(currentCL) +
421 ": " + trim(msg));
422 break;
423 } catch (final ExceptionInInitializerError e) {
424 // A static initializer block or the initializer code associated
425 // with a static variable on the log adapter class has thrown
426 // an exception.
427 //
428 // We treat this as meaning the adapter's underlying logging
429 // library could not be found.
430 final String msg = e.getMessage();
431 logDiagnostic("The log adapter '" + logAdapterClassName + "' is unable to initialize itself when loaded via class loader " +
432 objectId(currentCL) + ": " + trim(msg));
433 break;
434 } catch (final LogConfigurationException e) {
435 // call to handleFlawedHierarchy above must have thrown
436 // a LogConfigurationException, so just throw it on
437 throw e;
438 } catch (final Throwable t) {
439 handleThrowable(t); // may re-throw t
440 // handleFlawedDiscovery will determine whether this is a fatal
441 // problem or not. If it is fatal, then a LogConfigurationException
442 // will be thrown.
443 handleFlawedDiscovery(logAdapterClassName, t);
444 }
445 if (currentCL == null) {
446 break;
447 }
448 // try the parent class loader
449 // currentCL = currentCL.getParent();
450 currentCL = getParentClassLoader(currentCL);
451 }
452 if (logAdapterClass != null && affectState) {
453 // We've succeeded, so set instance fields
454 this.logClassName = logAdapterClassName;
455 this.logConstructor = constructor;
456 // Identify the {@code setLogFactory} method (if there is one)
457 try {
458 this.logMethod = logAdapterClass.getMethod("setLogFactory", logMethodSignature);
459 logDiagnostic("Found method setLogFactory(LogFactory) in '" + logAdapterClassName + "'");
460 } catch (final Throwable t) {
461 handleThrowable(t); // may re-throw t
462 this.logMethod = null;
463 logDiagnostic("[INFO] '" + logAdapterClassName + "' from class loader " + objectId(currentCL) + " does not declare optional method " +
464 "setLogFactory(LogFactory)");
465 }
466 logDiagnostic("Log adapter '" + logAdapterClassName + "' from class loader " + objectId(logAdapterClass.getClassLoader()) +
467 " has been selected for use.");
468 }
469 return logAdapter;
470 }
471
472 // Static Methods
473 //
474 // These methods only defined as workarounds for a java 1.2 bug;
475 // theoretically none of these are needed.
476
477 /**
478 * Attempts to create a Log instance for the given category name.
479 * Follows the discovery process described in the class Javadoc.
480 *
481 * @param logCategory the name of the log category
482 * @throws LogConfigurationException if an error in discovery occurs,
483 * or if no adapter at all can be instantiated
484 */
485 private Log discoverLogImplementation(final String logCategory)
486 throws LogConfigurationException {
487 if (isDiagnosticsEnabled()) {
488 logDiagnostic("Discovering a Log implementation...");
489 }
490 initConfiguration();
491 Log result = null;
492 // See if the user specified the Log implementation to use
493 final String specifiedLogClassName = findUserSpecifiedLogClassName();
494 if (specifiedLogClassName != null) {
495 if (isDiagnosticsEnabled()) {
496 logDiagnostic("Attempting to load user-specified log class '" + specifiedLogClassName + "'...");
497 }
498 result = createLogFromClass(specifiedLogClassName, logCategory, true);
499 if (result == null) {
500 final StringBuilder messageBuffer = new StringBuilder("User-specified log class '");
501 messageBuffer.append(specifiedLogClassName);
502 messageBuffer.append("' cannot be found or is not useable.");
503 // Mistyping or misspelling names is a common fault.
504 // Construct a good error message, if we can
505 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
506 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
507 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
508 informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
509 throw new LogConfigurationException(messageBuffer.toString());
510 }
511 return result;
512 }
513 // No user specified log; try to discover what's on the classpath
514 //
515 // Note that we deliberately loop here over classesToDiscover and
516 // expect method createLogFromClass to loop over the possible source
517 // class loaders. The effect is:
518 // for each discoverable log adapter
519 // for each possible class loader
520 // see if it works
521 //
522 // It appears reasonable at first glance to do the opposite:
523 // for each possible class loader
524 // for each discoverable log adapter
525 // see if it works
526 //
527 // The latter certainly has advantages for user-installable logging
528 // libraries such as Log4j; in a webapp for example this code should
529 // first check whether the user has provided any of the possible
530 // logging libraries before looking in the parent class loader.
531 // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4,
532 // and SimpleLog will always work in any JVM. So the loop would never
533 // ever look for logging libraries in the parent classpath. Yet many
534 // users would expect that putting Log4j there would cause it to be
535 // detected (and this is the historical JCL behavior). So we go with
536 // the first approach. A user that has bundled a specific logging lib
537 // in a webapp should use a commons-logging.properties file or a
538 // service file in META-INF to force use of that logging lib anyway,
539 // rather than relying on discovery.
540 if (isDiagnosticsEnabled()) {
541 logDiagnostic("No user-specified Log implementation; performing discovery using the standard supported logging implementations...");
542 }
543 for (int i = 0; i < DISCOVER_CLASSES.length && result == null; ++i) {
544 result = createLogFromClass(DISCOVER_CLASSES[i], logCategory, true);
545 }
546 if (result == null) {
547 throw new LogConfigurationException("No suitable Log implementation");
548 }
549 return result;
550 }
551
552 /**
553 * Checks system properties and the attribute map for
554 * a Log implementation specified by the user under the
555 * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}.
556 *
557 * @return class name specified by the user, or {@code null}
558 */
559 private String findUserSpecifiedLogClassName() {
560 if (isDiagnosticsEnabled()) {
561 logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'");
562 }
563 String specifiedClass = (String) getAttribute(LOG_PROPERTY);
564 if (specifiedClass == null) { // @deprecated
565 if (isDiagnosticsEnabled()) {
566 logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY_OLD + "'");
567 }
568 specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD);
569 }
570 if (specifiedClass == null) {
571 if (isDiagnosticsEnabled()) {
572 logDiagnostic("Trying to get log class from system property '" + LOG_PROPERTY + "'");
573 }
574 try {
575 specifiedClass = getSystemProperty(LOG_PROPERTY, null);
576 } catch (final SecurityException e) {
577 if (isDiagnosticsEnabled()) {
578 logDiagnostic("No access allowed to system property '" + LOG_PROPERTY + "' - " + e.getMessage());
579 }
580 }
581 }
582 if (specifiedClass == null) { // @deprecated
583 if (isDiagnosticsEnabled()) {
584 logDiagnostic("Trying to get log class from system property '" + LOG_PROPERTY_OLD + "'");
585 }
586 try {
587 specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null);
588 } catch (final SecurityException e) {
589 if (isDiagnosticsEnabled()) {
590 logDiagnostic("No access allowed to system property '" + LOG_PROPERTY_OLD + "' - " + e.getMessage());
591 }
592 }
593 }
594 // Remove any whitespace; it's never valid in a class name so its
595 // presence just means a user mistake. As we know what they meant,
596 // we may as well strip the spaces.
597 if (specifiedClass != null) {
598 specifiedClass = specifiedClass.trim();
599 }
600 return specifiedClass;
601 }
602
603 /**
604 * Gets the configuration attribute with the specified name (if any),
605 * or {@code null} if there is no such attribute.
606 *
607 * @param name Name of the attribute to return
608 */
609 @Override
610 public Object getAttribute(final String name) {
611 return attributes.get(name);
612 }
613
614 /**
615 * Gets an array containing the names of all currently defined
616 * configuration attributes. If there are no such attributes, a zero
617 * length array is returned.
618 */
619 @Override
620 public String[] getAttributeNames() {
621 return attributes.keySet().toArray(EMPTY_STRING_ARRAY);
622 }
623
624 /**
625 * Gets the class loader from which we should try to load the logging
626 * adapter classes.
627 * <p>
628 * This method usually returns the context class loader. However if it
629 * is discovered that the class loader which loaded this class is a child
630 * of the context class loader <em>and</em> the allowFlawedContext option
631 * has been set then the class loader which loaded this class is returned
632 * instead.
633 * </p>
634 * <p>
635 * The only time when the class loader which loaded this class is a
636 * descendant (rather than the same as or an ancestor of the context
637 * class loader) is when an app has created custom class loaders but
638 * failed to correctly set the context class loader. This is a bug in
639 * the calling application; however we provide the option for JCL to
640 * simply generate a warning rather than fail outright.
641 * </p>
642 */
643 private ClassLoader getBaseClassLoader() throws LogConfigurationException {
644 final ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class);
645 if (!useTCCL) {
646 return thisClassLoader;
647 }
648 final ClassLoader contextClassLoader = getContextClassLoaderInternal();
649 final ClassLoader baseClassLoader = getLowestClassLoader(contextClassLoader, thisClassLoader);
650 if (baseClassLoader == null) {
651 // The two class loaders are not part of a parent child relationship.
652 // In some classloading setups (e.g. JBoss with its
653 // UnifiedLoaderRepository) this can still work, so if user hasn't
654 // forbidden it, just return the contextClassLoader.
655 if (!allowFlawedContext) {
656 throw new LogConfigurationException(
657 "Bad class loader hierarchy; LogFactoryImpl was loaded via a class loader that is not related to the current context class loader.");
658 }
659 if (isDiagnosticsEnabled()) {
660 logDiagnostic(
661 "[WARNING] the context class loader is not part of a parent-child relationship with the class loader that loaded LogFactoryImpl.");
662 }
663 // If contextClassLoader were null, getLowestClassLoader() would
664 // have returned thisClassLoader. The fact we are here means
665 // contextClassLoader is not null, so we can just return it.
666 return contextClassLoader;
667 }
668 if (baseClassLoader != contextClassLoader) {
669 // We really should just use the contextClassLoader as the starting
670 // point for scanning for log adapter classes. However it is expected
671 // that there are a number of broken systems out there which create
672 // custom class loaders but fail to set the context class loader so
673 // we handle those flawed systems anyway.
674 if (!allowFlawedContext) {
675 throw new LogConfigurationException(
676 "Bad class loader hierarchy; LogFactoryImpl was loaded via a class loader that is not related to the current context class loader.");
677 }
678 if (isDiagnosticsEnabled()) {
679 logDiagnostic("Warning: the context class loader is an ancestor of the class loader that loaded LogFactoryImpl; it should be" +
680 " the same or a descendant. The application using commons-logging should ensure the context class loader is used correctly.");
681 }
682 }
683 return baseClassLoader;
684 }
685
686 /**
687 * Gets the setting for the user-configurable behavior specified by key.
688 * If nothing has explicitly been set, then return dflt.
689 */
690 private boolean getBooleanConfiguration(final String key, final boolean dflt) {
691 final String val = getConfigurationValue(key);
692 if (val == null) {
693 return dflt;
694 }
695 return Boolean.parseBoolean(val);
696 }
697
698 /**
699 * Attempt to find an attribute (see method setAttribute) or a
700 * system property with the provided name and return its value.
701 * <p>
702 * The attributes associated with this object are checked before
703 * system properties in case someone has explicitly called setAttribute,
704 * or a configuration property has been set in a commons-logging.properties
705 * file.
706 * </p>
707 *
708 * @return the value associated with the property, or null.
709 */
710 private String getConfigurationValue(final String property) {
711 if (isDiagnosticsEnabled()) {
712 logDiagnostic("[ENV] Trying to get configuration for item " + property);
713 }
714 final Object valueObj = getAttribute(property);
715 if (valueObj != null) {
716 if (isDiagnosticsEnabled()) {
717 logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property);
718 }
719 return valueObj.toString();
720 }
721 if (isDiagnosticsEnabled()) {
722 logDiagnostic("[ENV] No LogFactory attribute found for " + property);
723 }
724 try {
725 // warning: minor security hole here, in that we potentially read a system
726 // property that the caller cannot, then output it in readable form as a
727 // diagnostic message. However it's only ever JCL-specific properties
728 // involved here, so the harm is truly trivial.
729 final String value = getSystemProperty(property, null);
730 if (value != null) {
731 if (isDiagnosticsEnabled()) {
732 logDiagnostic("[ENV] Found system property [" + value + "] for " + property);
733 }
734 return value;
735 }
736 if (isDiagnosticsEnabled()) {
737 logDiagnostic("[ENV] No system property found for property " + property);
738 }
739 } catch (final SecurityException e) {
740 if (isDiagnosticsEnabled()) {
741 logDiagnostic("[ENV] Security prevented reading system property " + property);
742 }
743 }
744 if (isDiagnosticsEnabled()) {
745 logDiagnostic("[ENV] No configuration defined for item " + property);
746 }
747 return null;
748 }
749
750 /**
751 * Convenience method to derive a name from the specified class and
752 * call {@code getInstance(String)} with it.
753 *
754 * @param clazz Class for which a suitable Log name will be derived
755 * @throws LogConfigurationException if a suitable {@code Log}
756 * instance cannot be returned
757 */
758 @Override
759 public Log getInstance(final Class<?> clazz) throws LogConfigurationException {
760 return getInstance(clazz.getName());
761 }
762
763 /**
764 * Construct (if necessary) and return a {@code Log} instance,
765 * using the factory's current set of configuration attributes.
766 *
767 * <p><strong>NOTE</strong> - Depending upon the implementation of
768 * the {@code LogFactory} you are using, the {@code Log}
769 * instance you are returned may or may not be local to the current
770 * application, and may or may not be returned again on a subsequent
771 * call with the same name argument.</p>
772 *
773 * @param name Logical name of the {@code Log} instance to be
774 * returned (the meaning of this name is only known to the underlying
775 * logging implementation that is being wrapped)
776 *
777 * @throws LogConfigurationException if a suitable {@code Log}
778 * instance cannot be returned
779 */
780 @Override
781 public Log getInstance(final String name) throws LogConfigurationException {
782 return instances.computeIfAbsent(name, this::newInstance);
783 }
784
785 /**
786 * Gets the fully qualified Java class name of the {@link Log} implementation we will be using.
787 *
788 * @return the fully qualified Java class name of the {@link Log} implementation we will be using.
789 * @deprecated Never invoked by this class; subclasses should not assume it will be.
790 */
791 @Deprecated
792 protected String getLogClassName() {
793 if (logClassName == null) {
794 discoverLogImplementation(getClass().getName());
795 }
796 return logClassName;
797 }
798
799 /**
800 * Gets the {@code Constructor} that can be called to instantiate new {@link org.apache.commons.logging.Log} instances.
801 * <p>
802 * <strong>IMPLEMENTATION NOTE</strong> - Race conditions caused by calling this method from more than one thread are ignored, because the same
803 * {@code Constructor} instance will ultimately be derived in all circumstances.
804 * </p>
805 *
806 * @return the {@code Constructor} that can be called to instantiate new {@link org.apache.commons.logging.Log} instances.
807 * @throws LogConfigurationException if a suitable constructor cannot be returned
808 * @deprecated Never invoked by this class; subclasses should not assume it will be.
809 */
810 @Deprecated
811 protected Constructor<?> getLogConstructor() throws LogConfigurationException {
812 // Return the previously identified Constructor (if any)
813 if (logConstructor == null) {
814 discoverLogImplementation(getClass().getName());
815 }
816 return logConstructor;
817 }
818 // ------------------------------------------------------ Private Methods
819
820 /**
821 * Given two related class loaders, return the one which is a child of
822 * the other.
823 *
824 * @param c1 is a class loader (including the null class loader)
825 * @param c2 is a class loader (including the null class loader)
826 * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor,
827 * and null if neither is an ancestor of the other.
828 */
829 private ClassLoader getLowestClassLoader(final ClassLoader c1, final ClassLoader c2) {
830 // TODO: use AccessController when dealing with class loaders here
831 if (c1 == null) {
832 return c2;
833 }
834 if (c2 == null) {
835 return c1;
836 }
837 ClassLoader current;
838 // scan c1's ancestors to find c2
839 current = c1;
840 while (current != null) {
841 if (current == c2) {
842 return c1;
843 }
844 // current = current.getParent();
845 current = getParentClassLoader(current);
846 }
847 // scan c2's ancestors to find c1
848 current = c2;
849 while (current != null) {
850 if (current == c1) {
851 return c2;
852 }
853 // current = current.getParent();
854 current = getParentClassLoader(current);
855 }
856 return null;
857 }
858
859 /**
860 * Fetch the parent class loader of a specified class loader.
861 * <p>
862 * If a SecurityException occurs, null is returned.
863 * </p>
864 * <p>
865 * Note that this method is non-static merely so logDiagnostic is available.
866 * </p>
867 */
868 private ClassLoader getParentClassLoader(final ClassLoader cl) {
869 try {
870 return AccessController.doPrivileged((PrivilegedAction<ClassLoader>) () -> cl.getParent());
871 } catch (final SecurityException ex) {
872 logDiagnostic("[SECURITY] Unable to obtain parent class loader");
873 return null;
874 }
875 }
876
877 /**
878 * Generates an internal diagnostic logging of the discovery failure and
879 * then throws a {@code LogConfigurationException} that wraps
880 * the passed {@code Throwable}.
881 *
882 * @param logAdapterClassName is the class name of the Log implementation
883 * that could not be instantiated. Cannot be {@code null}.
884 * @param discoveryFlaw is the Throwable created by the class loader
885 * @throws LogConfigurationException ALWAYS
886 */
887 private void handleFlawedDiscovery(final String logAdapterClassName, final Throwable discoveryFlaw) {
888 if (isDiagnosticsEnabled()) {
889 logDiagnostic("Could not instantiate Log '" + logAdapterClassName + "' -- " + discoveryFlaw.getClass().getName() + ": " +
890 discoveryFlaw.getLocalizedMessage());
891 if (discoveryFlaw instanceof InvocationTargetException) {
892 // Ok, the lib is there but while trying to create a real underlying
893 // logger something failed in the underlying lib; display info about
894 // that if possible.
895 final InvocationTargetException ite = (InvocationTargetException) discoveryFlaw;
896 final Throwable cause = ite.getTargetException();
897 if (cause != null) {
898 logDiagnostic("... InvocationTargetException: " + cause.getClass().getName() + ": " + cause.getLocalizedMessage());
899 if (cause instanceof ExceptionInInitializerError) {
900 final ExceptionInInitializerError eiie = (ExceptionInInitializerError) cause;
901 final Throwable cause2 = eiie.getCause();
902 if (cause2 != null) {
903 final StringWriter sw = new StringWriter();
904 cause2.printStackTrace(new PrintWriter(sw, true));
905 logDiagnostic("... ExceptionInInitializerError: " + sw.toString());
906 }
907 }
908 }
909 }
910 }
911 if (!allowFlawedDiscovery) {
912 throw new LogConfigurationException(discoveryFlaw);
913 }
914 }
915
916 /**
917 * Report a problem loading the log adapter, then either return
918 * (if the situation is considered recoverable) or throw a
919 * LogConfigurationException.
920 * <p>
921 * There are two possible reasons why we successfully loaded the
922 * specified log adapter class then failed to cast it to a Log object:
923 * </p>
924 * <ol>
925 * <li>the specific class just doesn't implement the Log interface
926 * (user screwed up), or</li>
927 * <li> the specified class has bound to a Log class loaded by some other
928 * class loader; Log@ClassLoaderX cannot be cast to Log@ClassLoaderY.</li>
929 * </ol>
930 * <p>
931 * Here we try to figure out which case has occurred so we can give the
932 * user some reasonable feedback.
933 * </p>
934 *
935 * @param badClassLoader is the class loader we loaded the problem class from,
936 * ie it is equivalent to badClass.getClassLoader().
937 *
938 * @param badClass is a Class object with the desired name, but which
939 * does not implement Log correctly.
940 *
941 * @throws LogConfigurationException when the situation
942 * should not be recovered from.
943 */
944 private void handleFlawedHierarchy(final ClassLoader badClassLoader, final Class<?> badClass) throws LogConfigurationException {
945 boolean implementsLog = false;
946 final String logInterfaceName = Log.class.getName();
947 final Class<?>[] interfaces = badClass.getInterfaces();
948 for (final Class<?> element : interfaces) {
949 if (logInterfaceName.equals(element.getName())) {
950 implementsLog = true;
951 break;
952 }
953 }
954 if (implementsLog) {
955 // the class does implement an interface called Log, but
956 // it is in the wrong class loader
957 if (isDiagnosticsEnabled()) {
958 try {
959 final ClassLoader logInterfaceClassLoader = getClassLoader(Log.class);
960 logDiagnostic("Class '" + badClass.getName() + "' was found in class loader " + objectId(badClassLoader) +
961 ". It is bound to a Log interface which is not the one loaded from class loader " + objectId(logInterfaceClassLoader));
962 } catch (final Throwable t) {
963 handleThrowable(t); // may re-throw t
964 logDiagnostic("Error while trying to output diagnostics about bad class '" + badClass + "'");
965 }
966 }
967 if (!allowFlawedHierarchy) {
968 final StringBuilder msg = new StringBuilder();
969 msg.append("Terminating logging for this context ");
970 msg.append("due to bad log hierarchy. ");
971 msg.append("You have more than one version of '");
972 msg.append(Log.class.getName());
973 msg.append("' visible.");
974 if (isDiagnosticsEnabled()) {
975 logDiagnostic(msg.toString());
976 }
977 throw new LogConfigurationException(msg.toString());
978 }
979 if (isDiagnosticsEnabled()) {
980 final StringBuilder msg = new StringBuilder();
981 msg.append("Warning: bad log hierarchy. ");
982 msg.append("You have more than one version of '");
983 msg.append(Log.class.getName());
984 msg.append("' visible.");
985 logDiagnostic(msg.toString());
986 }
987 } else {
988 // this is just a bad adapter class
989 if (!allowFlawedDiscovery) {
990 final StringBuilder msg = new StringBuilder();
991 msg.append("Terminating logging for this context. ");
992 msg.append("Log class '");
993 msg.append(badClass.getName());
994 msg.append("' does not implement the Log interface.");
995 if (isDiagnosticsEnabled()) {
996 logDiagnostic(msg.toString());
997 }
998 throw new LogConfigurationException(msg.toString());
999 }
1000 if (isDiagnosticsEnabled()) {
1001 final StringBuilder msg = new StringBuilder();
1002 msg.append("[WARNING] Log class '");
1003 msg.append(badClass.getName());
1004 msg.append("' does not implement the Log interface.");
1005 logDiagnostic(msg.toString());
1006 }
1007 }
1008 }
1009
1010 /**
1011 * Appends message if the given name is similar to the candidate.
1012 *
1013 * @param messageBuffer {@code StringBuffer} the message should be appended to,
1014 * not null
1015 * @param name the (trimmed) name to be test against the candidate, not null
1016 * @param candidate the candidate name (not null)
1017 */
1018 private void informUponSimilarName(final StringBuilder messageBuffer, final String name, final String candidate) {
1019 if (name.equals(candidate)) {
1020 // Don't suggest a name that is exactly the same as the one the
1021 // user tried...
1022 return;
1023 }
1024 // If the user provides a name that is in the right package, and gets
1025 // the first 5 characters of the adapter class right (ignoring case),
1026 // then suggest the candidate adapter class name.
1027 if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) {
1028 messageBuffer.append(" Did you mean '");
1029 messageBuffer.append(candidate);
1030 messageBuffer.append("'?");
1031 }
1032 }
1033
1034 /**
1035 * Initializes a number of variables that control the behavior of this
1036 * class and that can be tweaked by the user. This is done when the first
1037 * logger is created, not in the constructor of this class, because we
1038 * need to give the user a chance to call method setAttribute in order to
1039 * configure this object.
1040 */
1041 private void initConfiguration() {
1042 allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true);
1043 allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true);
1044 allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true);
1045 }
1046
1047 /**
1048 * Initializes a string that uniquely identifies this instance,
1049 * including which class loader the object was loaded from.
1050 * <p>
1051 * This string will later be prefixed to each "internal logging" message
1052 * emitted, so that users can clearly see any unexpected behavior.
1053 * <p>
1054 * Note that this method does not detect whether internal logging is
1055 * enabled or not, nor where to output stuff if it is; that is all
1056 * handled by the parent LogFactory class. This method just computes
1057 * its own unique prefix for log messages.
1058 */
1059 private void initDiagnostics() {
1060 // It would be nice to include an identifier of the context class loader
1061 // that this LogFactoryImpl object is responsible for. However that
1062 // isn't possible as that information isn't available. It is possible
1063 // to figure this out by looking at the logging from LogFactory to
1064 // see the context & impl ids from when this object was instantiated,
1065 // in order to link the impl id output as this object's prefix back to
1066 // the context it is intended to manage.
1067 // Note that this prefix should be kept consistent with that
1068 // in LogFactory.
1069 @SuppressWarnings("unchecked")
1070 final Class<LogFactoryImpl> clazz = (Class<LogFactoryImpl>) this.getClass();
1071 final ClassLoader classLoader = getClassLoader(clazz);
1072 String classLoaderName;
1073 try {
1074 if (classLoader == null) {
1075 classLoaderName = "BOOTLOADER";
1076 } else {
1077 classLoaderName = objectId(classLoader);
1078 }
1079 } catch (final SecurityException e) {
1080 classLoaderName = "UNKNOWN";
1081 }
1082 diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] ";
1083 }
1084
1085 /**
1086 * Tests whether <em>JDK 1.3 with Lumberjack</em> logging available.
1087 *
1088 * @return whether <em>JDK 1.3 with Lumberjack</em> logging available.
1089 * @deprecated Never invoked by this class; subclasses should not assume it will be.
1090 */
1091 @Deprecated
1092 protected boolean isJdk13LumberjackAvailable() {
1093 return isLogLibraryAvailable("Jdk13Lumberjack", "org.apache.commons.logging.impl.Jdk13LumberjackLogger");
1094 }
1095
1096 /**
1097 * Tests {@code true} whether <em>JDK 1.4 or later</em> logging is available. Also checks that the {@code Throwable} class supports {@code getStackTrace()},
1098 * which is required by Jdk14Logger.
1099 *
1100 * @return Whether <em>JDK 1.4 or later</em> logging is available.
1101 * @deprecated Never invoked by this class; subclasses should not assume it will be.
1102 */
1103 @Deprecated
1104 protected boolean isJdk14Available() {
1105 return isLogLibraryAvailable("Jdk14", "org.apache.commons.logging.impl.Jdk14Logger");
1106 }
1107
1108 /**
1109 * Tests whether a <em>Log4J</em> implementation available.
1110 *
1111 * @return whether a <em>Log4J</em> implementation available.
1112 * @deprecated Never invoked by this class; subclasses should not assume it will be.
1113 */
1114 @Deprecated
1115 protected boolean isLog4JAvailable() {
1116 return isLogLibraryAvailable("Log4J", LOGGING_IMPL_LOG4J_LOGGER);
1117 }
1118
1119 /**
1120 * Tests whether a particular logging library is present and available for use. Note that this does <em>not</em> affect the future behavior of this class.
1121 */
1122 private boolean isLogLibraryAvailable(final String name, final String className) {
1123 if (isDiagnosticsEnabled()) {
1124 logDiagnostic("Checking for '" + name + "'.");
1125 }
1126 try {
1127 final Log log = createLogFromClass(className, this.getClass().getName(), // dummy category
1128 false);
1129 if (log == null) {
1130 if (isDiagnosticsEnabled()) {
1131 logDiagnostic("Did not find '" + name + "'.");
1132 }
1133 return false;
1134 }
1135 if (isDiagnosticsEnabled()) {
1136 logDiagnostic("Found '" + name + "'.");
1137 }
1138 return true;
1139 } catch (final LogConfigurationException e) {
1140 if (isDiagnosticsEnabled()) {
1141 logDiagnostic("Logging system '" + name + "' is available but not useable.");
1142 }
1143 return false;
1144 }
1145 }
1146
1147 /**
1148 * Output a diagnostic message to a user-specified destination (if the
1149 * user has enabled diagnostic logging).
1150 *
1151 * @param msg diagnostic message
1152 * @since 1.1
1153 */
1154 protected void logDiagnostic(final String msg) {
1155 if (isDiagnosticsEnabled()) {
1156 logRawDiagnostic(diagnosticPrefix + msg);
1157 }
1158 }
1159
1160 /**
1161 * Create and return a new {@link org.apache.commons.logging.Log} instance for the specified name.
1162 *
1163 * @param name Name of the new logger
1164 * @return a new {@link org.apache.commons.logging.Log}
1165 * @throws LogConfigurationException if a new instance cannot be created
1166 */
1167 protected Log newInstance(final String name) throws LogConfigurationException {
1168 Log instance;
1169 try {
1170 if (logConstructor == null) {
1171 instance = discoverLogImplementation(name);
1172 } else {
1173 final Object[] params = { name };
1174 instance = (Log) logConstructor.newInstance(params);
1175 }
1176 if (logMethod != null) {
1177 final Object[] params = { this };
1178 logMethod.invoke(instance, params);
1179 }
1180 return instance;
1181 } catch (final LogConfigurationException lce) {
1182 // this type of exception means there was a problem in discovery
1183 // and we've already output diagnostics about the issue, etc.;
1184 // just pass it on
1185 throw lce;
1186 } catch (final InvocationTargetException e) {
1187 // A problem occurred invoking the Constructor or Method
1188 // previously discovered
1189 final Throwable c = e.getTargetException();
1190 throw new LogConfigurationException(c == null ? e : c);
1191 } catch (final Throwable t) {
1192 handleThrowable(t); // may re-throw t
1193 // A problem occurred invoking the Constructor or Method
1194 // previously discovered
1195 throw new LogConfigurationException(t);
1196 }
1197 }
1198
1199 /**
1200 * Releases any internal references to previously created
1201 * {@link org.apache.commons.logging.Log}
1202 * instances returned by this factory. This is useful in environments
1203 * like servlet containers, which implement application reloading by
1204 * throwing away a ClassLoader. Dangling references to objects in that
1205 * class loader would prevent garbage collection.
1206 */
1207 @Override
1208 public void release() {
1209 logDiagnostic("Releasing all known loggers");
1210 instances.clear();
1211 }
1212
1213 /**
1214 * Remove any configuration attribute associated with the specified name.
1215 * If there is no such attribute, no action is taken.
1216 *
1217 * @param name Name of the attribute to remove
1218 */
1219 @Override
1220 public void removeAttribute(final String name) {
1221 attributes.remove(name);
1222 }
1223
1224 /**
1225 * Sets the configuration attribute with the specified name. Calling
1226 * this with a {@code null} value is equivalent to calling
1227 * {@code removeAttribute(name)}.
1228 * <p>
1229 * This method can be used to set logging configuration programmatically
1230 * rather than via system properties. It can also be used in code running
1231 * within a container (such as a webapp) to configure behavior on a
1232 * per-component level instead of globally as system properties would do.
1233 * To use this method instead of a system property, call
1234 * </p>
1235 * <pre>
1236 * LogFactory.getFactory().setAttribute(...)
1237 * </pre>
1238 * <p>
1239 * This must be done before the first Log object is created; configuration
1240 * changes after that point will be ignored.
1241 * </p>
1242 * <p>
1243 * This method is also called automatically if LogFactory detects a
1244 * commons-logging.properties file; every entry in that file is set
1245 * automatically as an attribute here.
1246 * </p>
1247 *
1248 * @param name Name of the attribute to set
1249 * @param value Value of the attribute to set, or {@code null}
1250 * to remove any setting for this attribute
1251 */
1252 @Override
1253 public void setAttribute(final String name, final Object value) {
1254 if (logConstructor != null) {
1255 logDiagnostic("setAttribute: call too late; configuration already performed.");
1256 }
1257 if (value == null) {
1258 attributes.remove(name);
1259 } else {
1260 attributes.put(name, value);
1261 }
1262 if (name.equals(TCCL_KEY)) {
1263 useTCCL = value != null && Boolean.parseBoolean(value.toString());
1264 }
1265 }
1266 }