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     */
017    package org.apache.logging.log4j.nosql.appender.mongodb;
018    
019    import org.apache.logging.log4j.Level;
020    import org.apache.logging.log4j.Logger;
021    import org.apache.logging.log4j.core.appender.AppenderLoggingException;
022    import org.apache.logging.log4j.nosql.appender.NoSqlConnection;
023    import org.apache.logging.log4j.nosql.appender.NoSqlObject;
024    import org.apache.logging.log4j.status.StatusLogger;
025    import org.apache.logging.log4j.util.Strings;
026    import org.bson.BSON;
027    import org.bson.Transformer;
028    
029    import com.mongodb.BasicDBObject;
030    import com.mongodb.DB;
031    import com.mongodb.DBCollection;
032    import com.mongodb.Mongo;
033    import com.mongodb.MongoException;
034    import com.mongodb.WriteConcern;
035    import com.mongodb.WriteResult;
036    
037    /**
038     * The MongoDB implementation of {@link NoSqlConnection}.
039     */
040    public final class MongoDbConnection implements NoSqlConnection<BasicDBObject, MongoDbObject> {
041    
042        private static final Logger LOGGER = StatusLogger.getLogger();
043    
044        static {
045            BSON.addEncodingHook(Level.class, new Transformer() {
046                @Override
047                public Object transform(final Object o) {
048                    if (o instanceof Level) {
049                        return ((Level) o).name();
050                    }
051                    return o;
052                }
053            });
054        }
055    
056        private final DBCollection collection;
057        private final Mongo mongo;
058        private final WriteConcern writeConcern;
059    
060        public MongoDbConnection(final DB database, final WriteConcern writeConcern, final String collectionName) {
061            this.mongo = database.getMongo();
062            this.collection = database.getCollection(collectionName);
063            this.writeConcern = writeConcern;
064        }
065    
066        @Override
067        public MongoDbObject createObject() {
068            return new MongoDbObject();
069        }
070    
071        @Override
072        public MongoDbObject[] createList(final int length) {
073            return new MongoDbObject[length];
074        }
075    
076        @Override
077        public void insertObject(final NoSqlObject<BasicDBObject> object) {
078            try {
079                final WriteResult result = this.collection.insert(object.unwrap(), this.writeConcern);
080                if (Strings.isNotEmpty(result.getError())) {
081                    throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " +
082                            result.getError() + '.');
083                }
084            } catch (final MongoException e) {
085                throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " + e.getMessage(),
086                        e);
087            }
088        }
089    
090        @Override
091        public void close() {
092            // there's no need to call this.mongo.close() since that literally closes the connection
093            // MongoDBClient uses internal connection pooling
094            // for more details, see LOG4J2-591
095        }
096    
097        @Override
098        public boolean isClosed() {
099            return !this.mongo.getConnector().isOpen();
100        }
101    
102        /**
103         * To prevent class loading issues during plugin discovery, this code cannot live within MongoDbProvider. This
104         * is because of how Java treats references to Exception classes different from references to other classes. When
105         * Java loads a class, it normally won't load that class's dependent classes until and unless A) they are used, B)
106         * the class being loaded extends or implements those classes, or C) those classes are the types of static members
107         * in the class. However, exceptions that a class uses are always loaded when the class is loaded, even before
108         * they are actually used.
109         *
110         * @param database The database to authenticate
111         * @param username The username to authenticate with
112         * @param password The password to authenticate with
113         */
114        static void authenticate(final DB database, final String username, final String password) {
115            try {
116                if (!database.authenticate(username, password.toCharArray())) {
117                    LOGGER.error("Failed to authenticate against MongoDB server. Unknown error.");
118                }
119            } catch (final MongoException e) {
120                LOGGER.error("Failed to authenticate against MongoDB: " + e.getMessage(), e);
121            } catch (final IllegalStateException e) {
122                LOGGER.error("Factory-supplied MongoDB database connection already authenticated with different" +
123                        "credentials but lost connection.", e);
124            }
125        }
126    }