Detective Nick is one the case! :) Do those other projects use third
party line for this or roll their own?

Gary

On Aug 17, 2013, at 4:17, Nick Williams <[email protected]> wrote:

> Solved it!
>
> And you're never gonna believe what I learned tonight...(well, maybe you will)
>
> I solved the error by changing this:
>
> public final class MongoDBProvider implements 
> NoSQLProvider<MongoDBConnection> {
>    ...
> +                try {
> +                    if (!database.authenticate(username, 
> password.toCharArray())) {
> +                        LOGGER.error("Failed to authenticate against MongoDB 
> server. Unknown error.");
> +                    }
> +                } catch (MongoException e) {
> +                    LOGGER.error("Failed to authenticate against MongoDB: " 
> + e.getMessage(), e);
> +                } catch (IllegalStateException e) {
> +                    LOGGER.error("Factory-supplied MongoDB database 
> connection already authenticated with different" +
> +                            "credentials but lost connection.");
> +                }
>    ...
> }
>
> To this:
>
> public final class MongoDBProvider implements 
> NoSQLProvider<MongoDBConnection> {
>    ...
> +                MongoDBConnection.authenticate(database, username, password);
>    ...
> }
>
> public final class MongoDBConnection implements 
> NoSQLConnection<BasicDBObject, MongoDBObject> {
>    ...
> +    static void authenticate(final DB database, final String username, final 
> String password) {
> +        try {
> +            if (!database.authenticate(username, password.toCharArray())) {
> +                LOGGER.error("Failed to authenticate against MongoDB server. 
> Unknown error.");
> +            }
> +        } catch (final MongoException e) {
> +            LOGGER.error("Failed to authenticate against MongoDB: " + 
> e.getMessage(), e);
> +        } catch (final IllegalStateException e) {
> +            LOGGER.error("Factory-supplied MongoDB database connection 
> already authenticated with different" +
> +                    "credentials but lost connection.");
> +        }
> +    }
>    ...
> }
>
> Crazy, right!? Here's what I've learned:
>
> The errors were occurring in tests for the Log4j 1.2 API and the SLF4J 
> Bridge. These tests use the core Logger which triggers plugin discovery. In 
> order to scan for annotations, plugin discovery loads the MongoDBProvider, 
> CouchDBProvider, and JPAAppender classes, among many others, all of which 
> have transitive dependencies that are not on the classpath for running the 
> unit tests for Log4j 1.2 API and SLF4J. So how did it ever work in the first 
> place?
>
> As you may already know, when Java loads a class it also automatically loads 
> any classes it extends or implements, any classes that are the types of 
> static members of that class, any static inner classes, and any classes used 
> within the static initializer. It doesn't load any other classes that class 
> uses in any methods or constructors or as instance members--like 
> com.mongodb.DB or javax.persistence.*--until the code that uses them actually 
> executes for the first time. Because of this, we can do something like load 
> the MongoDBProvider class to scan for @Plugin annotations even though the 
> com.mongodb classes it uses are not on the classpath (as long as they aren't 
> static members of or extended by the MongoDBProvider, that is).
>
> However, Java has a special behavior with exceptions. Because we have these 
> lovely things called checked exceptions that methods must declare to be 
> thrown, exceptions are naturally part of a class's interface. Thus, when Java 
> loads a class it must load the exceptions the class's methods might throw so 
> that it can complete the interface in memory 
> (java.lang.Class.getMethod("someMethod").getExceptionTypes()). Likely for 
> performance reasons, it doesn't differentiate between exceptions that are 
> actually declared to be thrown and exceptions that are just used (caught). 
> ANY dependent classes that are exceptions are loaded when the class loads, 
> even if they're just caught exceptions. This is why this all worked until I 
> started using an exception from a transitive dependency within a plugin class 
> (MongoDBProvider).
>
> (Incidentally, it's also why more advanced class-scanning projects like 
> Spring, Hibernate and Tomcat don't load classes using a ClassLoader just to 
> scan for annotations. Instead, they inspect the byte code manually to scan 
> for annotations, preventing such class loading errors during discovery phases 
> and also saving memory resources since Classes aren't usually garbage 
> collected. It might be worthwhile to look into doing something similar in 
> Log4j plugin discovery. I don't know how much effort would be involved.)
>
> I haven't confirmed any of this with JLS documentation because no amount of 
> Google searching for the combination of "class loading" and "exception" 
> brings up anything other than 10,000,000 people asking questions about what's 
> wrong with their classpath. I simply can't find that needle in a planet full 
> of haystacks. But my thorough experimentation has some pretty clear results. 
> This is exactly what's happening.
>
> Nick
>
> On Aug 17, 2013, at 12:44 AM, Ralph Goers wrote:
>
>> I'll reiterate what I wrote. Catch the RuntimeException and then do
>>
>> if (e.class.getName().equals("com.mongodb.MongoException")) {
>>  LOGGER.error("...");
>> } else {
>>  throw e;
>> }
>>
>> This should give you the same behavior.
>>
>> Ralph
>>
>> On Aug 16, 2013, at 9:49 PM, Nick Williams wrote:
>>
>>> That approach concerns me. Catching RuntimeException essentially opens it 
>>> up to thousands of possible exceptions that could be the cause, as opposed 
>>> to looking for that exact cause. I suppose I don't have a choice, though. 
>>> This apparently just isn't going to work.
>>>
>>> Definitely agreed that there is too much going on for a simple Exception 
>>> class.
>>>
>>> :-/
>>>
>>> Nick
>>>
>>> On Aug 16, 2013, at 11:45 PM, Ralph Goers wrote:
>>>
>>>> After following the chain of stuff that gets brought in via BSONObject I 
>>>> still recommend the approach in my other email of just catching 
>>>> RuntimeException.  A bunch of other classes are being referenced, one of 
>>>> which is creating a static Logger from java.util.logging. I have no idea 
>>>> why that might fail but there is just way too much going on for a simple 
>>>> Exception class.
>>>>
>>>> Ralph
>>>>
>>>> On Aug 16, 2013, at 9:26 PM, Nick Williams wrote:
>>>>
>>>>> https://github.com/mongodb/mongo-java-driver/blob/master/src/main/com/mongodb/DB.java
>>>>>
>>>>> That also shows an import for org.bson.BSONObject, but the tests still 
>>>>> run with just DB and no MongoException. org.bson is in the 
>>>>> org.mongodb:mongo-java-driver JAR file. So, no, that's not the problem. 
>>>>> There's something else...
>>>>>
>>>>> Nick
>>>>>
>>>>> On Aug 16, 2013, at 11:20 PM, Ralph Goers wrote:
>>>>>
>>>>>> https://github.com/mongodb/mongo-java-driver/blob/master/src/main/com/mongodb/MongoException.java
>>>>>>  shows an import for org.bson.BSONObject.  The pom.xml for 
>>>>>> mongo-java-driver doesn't contain a transitive dependency for that and 
>>>>>> mvn dependency:tree on core doesn't show it.
>>>>>>
>>>>>> Ralph
>>>>>>
>>>>>>
>>>>>> On Aug 16, 2013, at 3:48 PM, Nick Williams wrote:
>>>>>>
>>>>>>> Guys, I'm having a hard time with this simple fix that should have 
>>>>>>> taken five minutes. I'm getting test failures due to 
>>>>>>> NoClassDefFoundErrors that shouldn't happen.
>>>>>>>
>>>>>>> Here are the tests in error:
>>>>>>>  CategoryTest.setupClass:52 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testTraceWithException:415 ? NoClassDefFound 
>>>>>>> com/mongodb/MongoExcep...
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testLog:459 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testRB1:295 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testRB2:314 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testRB3:334 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testTrace:388 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testAdditivity1:119 ? NoClassDefFound 
>>>>>>> com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testAdditivity2:144 ? NoClassDefFound 
>>>>>>> com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testAdditivity3:183 ? NoClassDefFound 
>>>>>>> com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testIsTraceEnabled:443 ? NoClassDefFound 
>>>>>>> com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.testExists:355 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggerTest.tearDown:75 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggingTest.setupClass:44 ? NoClassDefFound com/mongodb/MongoException
>>>>>>>  LoggingTest.cleanupClass:49 NullPointer
>>>>>>>
>>>>>>> Here's the code I added:
>>>>>>>
>>>>>>>                try {
>>>>>>>                    if (!database.authenticate(username, 
>>>>>>> password.toCharArray())) {
>>>>>>>                        LOGGER.error("Failed to authenticate against 
>>>>>>> MongoDB server. Unknown error.");
>>>>>>>                    }
>>>>>>>                } catch (MongoException e) {
>>>>>>>                    LOGGER.error("Failed to authenticate against 
>>>>>>> MongoDB: " + e.getMessage(), e);
>>>>>>>                } catch (IllegalStateException e) {
>>>>>>>                    LOGGER.error("Factory-supplied MongoDB database 
>>>>>>> connection already authenticated with different" +
>>>>>>>                            "credentials but lost connection.");
>>>>>>>                }
>>>>>>>
>>>>>>> Problem is, "database" is an instance of com.mongodb.DB, which is in 
>>>>>>> the same JAR as com.mongodb.MongoException. If I remove this code, the 
>>>>>>> tests pass. How is this possible? The DB instance is there with or 
>>>>>>> without this new code, which means the JAR is on the classpath, which 
>>>>>>> means MongoException should be on the classpath.
>>>>>>>
>>>>>>> Very confused...
>>>>>>>
>>>>>>> Nick
>>>>>>>
>>>>>>> On Aug 16, 2013, at 5:13 PM, Gary Gregory wrote:
>>>>>>>
>>>>>>>> Thank you for the update Nick!
>>>>>>>> :)
>>>>>>>> Gary
>>>>>>>>
>>>>>>>>
>>>>>>>> On Fri, Aug 16, 2013 at 5:39 PM, Nick Williams 
>>>>>>>> <[email protected]> wrote:
>>>>>>>> Answers inline.
>>>>>>>>
>>>>>>>> On Aug 14, 2013, at 2:10 AM, YuCheng Ting wrote:
>>>>>>>>
>>>>>>>>> Hi all,
>>>>>>>>>
>>>>>>>>> I use beta8 log4j2 and wrote log4j2.xml like example in document 
>>>>>>>>> (http://logging.apache.org/log4j/2.x/manual/appenders.html#NoSQLAppender
>>>>>>>>>  ):
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> <appenders>
>>>>>>>>>    <NoSql name="databaseAppender">
>>>>>>>>>        <MongoDb databaseName="applicationDb" 
>>>>>>>>> collectionName="applicationLog"
>>>>>>>>>            server="mongo.example.org"
>>>>>>>>>            username="loggingUser" password="abc123" />
>>>>>>>>>    </NoSql>
>>>>>>>>> </appenders>
>>>>>>>>
>>>>>>>> Yep. That's correct.
>>>>>>>>
>>>>>>>>> but I get the two exception:
>>>>>>>>>
>>>>>>>>> 1, "can't serialize class org.apache.logging.log4j.Level" exception 
>>>>>>>>> in (BasicBSONEncoder.java:270), I read the code and add follow code 
>>>>>>>>> in my project before logging, it gone.
>>>>>>>>>
>>>>>>>>>        BSON.addEncodingHook(org.apache.logging.log4j.Level.class, new 
>>>>>>>>> Transformer() {
>>>>>>>>>            @Override
>>>>>>>>>            public Object transform(Object o) {
>>>>>>>>>                return o.toString();
>>>>>>>>>            }
>>>>>>>>>        });
>>>>>>>>
>>>>>>>> This bug was reported and fixed a few weeks ago. The fix will be in 
>>>>>>>> the next version, or you can compile locally. 
>>>>>>>> https://issues.apache.org/jira/browse/LOG4J2-330
>>>>>>>>
>>>>>>>>> 2, “not authorized for insert test.log”, because my MongoDB need auth 
>>>>>>>>> to write, but the the "username" and "password" attributes in 
>>>>>>>>> log4j2.xml is nearly useless, after I read source code, found it NOT 
>>>>>>>>> auth in
>>>>>>>>>
>>>>>>>>> org.apache.logging.log4j.core.appender.db.nosql.mongo.MongoDBProvider.createNoSQLProvider
>>>>>>>>> source code line 181 after check username and password and 
>>>>>>>>> com.mongodb.DB.authenticate never be called.
>>>>>>>>
>>>>>>>> This is a bug. I'm reporting it and fixing it now. The fix will be in 
>>>>>>>> the next version, or you can compile locally (after I get the change 
>>>>>>>> committed, of course).
>>>>>>>>
>>>>>>>>> so I change log4j2.xml :
>>>>>>>>>
>>>>>>>>> <NoSql name="mongodb">
>>>>>>>>>             <MongoDb collectionName="log" databaseName="test"
>>>>>>>>>                         
>>>>>>>>> factoryClassName="com.yuchs.test.log4j.MainTest"
>>>>>>>>>                        factoryMethodName="getMongoClient" />
>>>>>>>>> </NoSql>
>>>>>>>>>
>>>>>>>>> and create MongoClient and call com.mongodb.DB.authenticate method in 
>>>>>>>>> com.yuchs.test.log4j.MainTest.getMongoClient.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> This is my question:
>>>>>>>>>
>>>>>>>>> 1, Why not add BSON.addEncodingHook code into log4j2 project to avoid 
>>>>>>>>> basic exception ? or another rule of method I don't know ?
>>>>>>>>>
>>>>>>>>> 2, Why not auth DB in log4j2 project if password and username is set 
>>>>>>>>> in log4j2.xml ? or another rule of method I don't know ?
>>>>>>>>>
>>>>>>>>> Thanks everyone!
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> E-Mail: [email protected] | [email protected]
>>>>>>>> Java Persistence with Hibernate, Second Edition
>>>>>>>> JUnit in Action, Second Edition
>>>>>>>> Spring Batch in Action
>>>>>>>> Blog: http://garygregory.wordpress.com
>>>>>>>> Home: http://garygregory.com/
>>>>>>>> Tweet! http://twitter.com/GaryGregory
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to