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.nosql.appender.mongodb;
018
019import org.apache.logging.log4j.Level;
020import org.apache.logging.log4j.Logger;
021import org.apache.logging.log4j.core.appender.AppenderLoggingException;
022import org.apache.logging.log4j.nosql.appender.NoSqlConnection;
023import org.apache.logging.log4j.nosql.appender.NoSqlObject;
024import org.apache.logging.log4j.status.StatusLogger;
025import org.apache.logging.log4j.util.Strings;
026import org.bson.BSON;
027import org.bson.Transformer;
028
029import com.mongodb.BasicDBObject;
030import com.mongodb.DB;
031import com.mongodb.DBCollection;
032import com.mongodb.Mongo;
033import com.mongodb.MongoException;
034import com.mongodb.WriteConcern;
035import com.mongodb.WriteResult;
036
037/**
038 * The MongoDB implementation of {@link NoSqlConnection}.
039 */
040public 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}