View Javadoc
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    *      http://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  package org.apache.logging.log4j.nosql.appender.mongodb;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.logging.log4j.Logger;
25  import org.apache.logging.log4j.core.appender.AbstractAppender;
26  import org.apache.logging.log4j.core.config.plugins.Plugin;
27  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
28  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
29  import org.apache.logging.log4j.core.util.Loader;
30  import org.apache.logging.log4j.core.util.NameUtil;
31  import org.apache.logging.log4j.nosql.appender.NoSqlProvider;
32  import org.apache.logging.log4j.status.StatusLogger;
33  import org.apache.logging.log4j.util.Strings;
34  
35  import com.mongodb.DB;
36  import com.mongodb.MongoClient;
37  import com.mongodb.MongoCredential;
38  import com.mongodb.ServerAddress;
39  import com.mongodb.WriteConcern;
40  
41  /**
42   * The MongoDB implementation of {@link NoSqlProvider}.
43   */
44  @Plugin(name = "MongoDb", category = "Core", printObject = true)
45  public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {
46      
47      private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
48      private static final Logger LOGGER = StatusLogger.getLogger();
49  
50      private final String collectionName;
51      private final DB database;
52      private final String description;
53      private final WriteConcern writeConcern;
54  
55      private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName,
56              final String description) {
57          this.database = database;
58          this.writeConcern = writeConcern;
59          this.collectionName = collectionName;
60          this.description = "mongoDb{ " + description + " }";
61      }
62  
63      @Override
64      public MongoDbConnection getConnection() {
65          return new MongoDbConnection(this.database, this.writeConcern, this.collectionName);
66      }
67  
68      @Override
69      public String toString() {
70          return this.description;
71      }
72  
73      /**
74       * Factory method for creating a MongoDB provider within the plugin manager.
75       *
76       * @param collectionName The name of the MongoDB collection to which log events should be written.
77       * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to
78       *                             {@link WriteConcern#ACKNOWLEDGED}.
79       * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern
80       *                                      constant. Defaults to {@link WriteConcern}.
81       * @param databaseName The name of the MongoDB database containing the collection to which log events should be
82       *                     written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}.
83       * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with
84       *               {@code factoryClassName&factoryMethodName!=null}.
85       * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually
86       *             exclusive with {@code factoryClassName&factoryMethodName!=null}.
87       * @param userName The username to authenticate against the MongoDB server with.
88       * @param password The password to authenticate against the MongoDB server with.
89       * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a
90       *                         {@link DB} or a {@link MongoClient}.
91       * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory
92       *                          class.
93       * @return a new MongoDB provider.
94       */
95      @PluginFactory
96      public static MongoDbProvider createNoSqlProvider(
97              @PluginAttribute("collectionName") final String collectionName,
98              @PluginAttribute("writeConcernConstant") final String writeConcernConstant,
99              @PluginAttribute("writeConcernConstantClass") final String writeConcernConstantClassName,
100             @PluginAttribute("databaseName") final String databaseName,
101             @PluginAttribute("server") final String server,
102             @PluginAttribute("port") final String port,
103             @PluginAttribute("userName") final String userName,
104             @PluginAttribute(value = "password", sensitive = true) final String password,
105             @PluginAttribute("factoryClassName") final String factoryClassName,
106             @PluginAttribute("factoryMethodName") final String factoryMethodName) {
107         DB database;
108         String description;
109         if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
110             try {
111                 final Class<?> factoryClass = Loader.loadClass(factoryClassName);
112                 final Method method = factoryClass.getMethod(factoryMethodName);
113                 final Object object = method.invoke(null);
114 
115                 if (object instanceof DB) {
116                     database = (DB) object;
117                 } else if (object instanceof MongoClient) {
118                     if (Strings.isNotEmpty(databaseName)) {
119                         database = ((MongoClient) object).getDB(databaseName);
120                     } else {
121                         LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
122                                 + "required.", factoryClassName, factoryMethodName);
123                         return null;
124                     }
125                 } else if (object == null) {
126                     LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName);
127                     return null;
128                 } else {
129                     LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName,
130                             factoryMethodName, object.getClass().getName());
131                     return null;
132                 }
133 
134                 description = "database=" + database.getName();
135                 final List<ServerAddress> addresses = database.getMongo().getAllAddress();
136                 if (addresses.size() == 1) {
137                     description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort();
138                 } else {
139                     description += ", servers=[";
140                     for (final ServerAddress address : addresses) {
141                         description += " { " + address.getHost() + ", " + address.getPort() + " } ";
142                     }
143                     description += "]";
144                 }
145             } catch (final ClassNotFoundException e) {
146                 LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
147                 return null;
148             } catch (final NoSuchMethodException e) {
149                 LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
150                         factoryMethodName, e);
151                 return null;
152             } catch (final Exception e) {
153                 LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName,
154                         e);
155                 return null;
156             }
157         } else if (Strings.isNotEmpty(databaseName)) {
158             final List<MongoCredential> credentials = new ArrayList<>();
159             description = "database=" + databaseName;
160             if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
161                 description += ", username=" + userName + ", passwordHash="
162                         + NameUtil.md5(password + MongoDbProvider.class.getName());
163                 credentials.add(MongoCredential.createCredential(userName, databaseName, password.toCharArray()));
164             }
165             try {
166                 if (Strings.isNotEmpty(server)) {
167                     final int portInt = AbstractAppender.parseInt(port, 0);
168                     description += ", server=" + server;
169                     if (portInt > 0) {
170                         description += ", port=" + portInt;
171                         database = new MongoClient(new ServerAddress(server, portInt), credentials).getDB(databaseName);
172                     } else {
173                         database = new MongoClient(new ServerAddress(server), credentials).getDB(databaseName);
174                     }
175                 } else {
176                     database = new MongoClient(new ServerAddress(), credentials).getDB(databaseName);
177                 }
178             } catch (final Exception e) {
179                 LOGGER.error(
180                         "Failed to obtain a database instance from the MongoClient at server [{}] and " + "port [{}].",
181                         server, port);
182                 return null;
183             }
184         } else {
185             LOGGER.error("No factory method was provided so the database name is required.");
186             return null;
187         }
188 
189         try {
190             database.getCollectionNames(); // Check if the database actually requires authentication
191         } catch (final Exception e) {
192             LOGGER.error(
193                     "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
194                     e);
195             return null;
196         }
197 
198         final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
199 
200         return new MongoDbProvider(database, writeConcern, collectionName, description);
201     }
202 
203     private static WriteConcern toWriteConcern(final String writeConcernConstant,
204             final String writeConcernConstantClassName) {
205         WriteConcern writeConcern;
206         if (Strings.isNotEmpty(writeConcernConstant)) {
207             if (Strings.isNotEmpty(writeConcernConstantClassName)) {
208                 try {
209                     final Class<?> writeConcernConstantClass = Loader.loadClass(writeConcernConstantClassName);
210                     final Field field = writeConcernConstantClass.getField(writeConcernConstant);
211                     writeConcern = (WriteConcern) field.get(null);
212                 } catch (final Exception e) {
213                     LOGGER.error("Write concern constant [{}.{}] not found, using default.",
214                             writeConcernConstantClassName, writeConcernConstant);
215                     writeConcern = DEFAULT_WRITE_CONCERN;
216                 }
217             } else {
218                 writeConcern = WriteConcern.valueOf(writeConcernConstant);
219                 if (writeConcern == null) {
220                     LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
221                     writeConcern = DEFAULT_WRITE_CONCERN;
222                 }
223             }
224         } else {
225             writeConcern = DEFAULT_WRITE_CONCERN;
226         }
227         return writeConcern;
228     }
229 }