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.mongodb3;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  
22  import org.apache.logging.log4j.Logger;
23  import org.apache.logging.log4j.core.Core;
24  import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
25  import org.apache.logging.log4j.core.config.plugins.Plugin;
26  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
27  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
28  import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
29  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
30  import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
31  import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
32  import org.apache.logging.log4j.core.filter.AbstractFilterable;
33  import org.apache.logging.log4j.status.StatusLogger;
34  import org.apache.logging.log4j.util.LoaderUtil;
35  import org.apache.logging.log4j.util.Strings;
36  import org.bson.codecs.configuration.CodecRegistries;
37  import org.bson.codecs.configuration.CodecRegistry;
38  
39  import com.mongodb.MongoClient;
40  import com.mongodb.MongoClientOptions;
41  import com.mongodb.MongoCredential;
42  import com.mongodb.ServerAddress;
43  import com.mongodb.WriteConcern;
44  import com.mongodb.client.MongoDatabase;
45  
46  /**
47   * The MongoDB implementation of {@link NoSqlProvider}.using the MongoDB driver version 3 API.
48   */
49  @Plugin(name = "MongoDb3", category = Core.CATEGORY_NAME, printObject = true)
50  public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {
51  
52      public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
53              implements org.apache.logging.log4j.core.util.Builder<MongoDbProvider> {
54  
55          // @formatter:off
56          private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries(
57                          CodecRegistries.fromCodecs(LevelCodec.INSTANCE),
58                          MongoClient.getDefaultCodecRegistry());
59          // @formatter:on
60  
61          private static WriteConcern toWriteConcern(final String writeConcernConstant,
62                  final String writeConcernConstantClassName) {
63              WriteConcern writeConcern;
64              if (Strings.isNotEmpty(writeConcernConstant)) {
65                  if (Strings.isNotEmpty(writeConcernConstantClassName)) {
66                      try {
67                          final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName);
68                          final Field field = writeConcernConstantClass.getField(writeConcernConstant);
69                          writeConcern = (WriteConcern) field.get(null);
70                      } catch (final Exception e) {
71                          LOGGER.error("Write concern constant [{}.{}] not found, using default.",
72                                  writeConcernConstantClassName, writeConcernConstant);
73                          writeConcern = DEFAULT_WRITE_CONCERN;
74                      }
75                  } else {
76                      writeConcern = WriteConcern.valueOf(writeConcernConstant);
77                      if (writeConcern == null) {
78                          LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
79                          writeConcern = DEFAULT_WRITE_CONCERN;
80                      }
81                  }
82              } else {
83                  writeConcern = DEFAULT_WRITE_CONCERN;
84              }
85              return writeConcern;
86          }
87  
88          @PluginBuilderAttribute
89          @Required(message = "No collection name provided")
90          private String collectionName;
91  
92          @PluginBuilderAttribute
93          private int collectionSize = DEFAULT_COLLECTION_SIZE;
94  
95          @PluginBuilderAttribute
96          @Required(message = "No database name provided")
97          private String databaseName;
98  
99          @PluginBuilderAttribute
100         private String factoryClassName;
101 
102         @PluginBuilderAttribute
103         private String factoryMethodName;
104 
105         @PluginBuilderAttribute("capped")
106         private boolean capped = false;
107 
108         @PluginBuilderAttribute(sensitive = true)
109         private String password;
110 
111         @PluginBuilderAttribute
112         @ValidPort
113         private String port = "" + DEFAULT_PORT;
114 
115         @PluginBuilderAttribute
116         @ValidHost
117         private String server = "localhost";
118 
119         @PluginBuilderAttribute
120         private String userName;
121 
122         @PluginBuilderAttribute
123         private String writeConcernConstant;
124 
125         @PluginBuilderAttribute
126         private String writeConcernConstantClassName;
127 
128         @SuppressWarnings("resource")
129         @Override
130         public MongoDbProvider build() {
131             MongoDatabase database;
132             String description;
133             MongoClient mongoClient = null;
134 
135             if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
136                 try {
137                     final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
138                     final Method method = factoryClass.getMethod(factoryMethodName);
139                     final Object object = method.invoke(null);
140 
141                     if (object instanceof MongoDatabase) {
142                         database = (MongoDatabase) object;
143                     } else if (object instanceof MongoClient) {
144                         if (Strings.isNotEmpty(databaseName)) {
145                             database = ((MongoClient) object).getDatabase(databaseName);
146                         } else {
147                             LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
148                                     + "required.", factoryClassName, factoryMethodName);
149                             return null;
150                         }
151                     } else {
152                         if (object == null) {
153                             LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName,
154                                     factoryMethodName);
155                         } else {
156                             LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].",
157                                     factoryClassName, factoryMethodName, object.getClass().getName());
158                         }
159                         return null;
160                     }
161 
162                     final String databaseName = database.getName();
163                     description = "database=" + databaseName;
164                 } catch (final ClassNotFoundException e) {
165                     LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
166                     return null;
167                 } catch (final NoSuchMethodException e) {
168                     LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
169                             factoryMethodName, e);
170                     return null;
171                 } catch (final Exception e) {
172                     LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName,
173                             factoryMethodName, e);
174                     return null;
175                 }
176             } else if (Strings.isNotEmpty(databaseName)) {
177                 MongoCredential mongoCredential = null;
178                 description = "database=" + databaseName;
179                 if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
180                     description += ", username=" + userName;
181                     mongoCredential = MongoCredential.createCredential(userName, databaseName, password.toCharArray());
182                 }
183                 try {
184                     final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
185                     description += ", server=" + server + ", port=" + portInt;
186                     final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
187                     // @formatter:off
188                     final MongoClientOptions options = MongoClientOptions.builder()
189                             .codecRegistry(CODEC_REGISTRIES)
190                             .writeConcern(writeConcern)
191                             .build();
192                     // @formatter:on
193                     final ServerAddress serverAddress = new ServerAddress(server, portInt);
194                     mongoClient = mongoCredential == null ?
195                     // @formatter:off
196                             new MongoClient(serverAddress, options) :
197                             new MongoClient(serverAddress, mongoCredential, options);
198                     // @formatter:on
199                     database = mongoClient.getDatabase(databaseName);
200                 } catch (final Exception e) {
201                     LOGGER.error("Failed to obtain a database instance from the MongoClient at server [{}] and "
202                             + "port [{}].", server, port);
203                     close(mongoClient);
204                     return null;
205                 }
206             } else {
207                 LOGGER.error("No factory method was provided so the database name is required.");
208                 close(mongoClient);
209                 return null;
210             }
211 
212             try {
213                 database.listCollectionNames().first(); // Check if the database actually requires authentication
214             } catch (final Exception e) {
215                 LOGGER.error(
216                         "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
217                         e);
218                 close(mongoClient);
219                 return null;
220             }
221 
222             return new MongoDbProvider(mongoClient, database, collectionName, capped, collectionSize, description);
223         }
224 
225         private void close(final MongoClient mongoClient) {
226             if (mongoClient != null) {
227                 mongoClient.close();
228             }
229         }
230 
231         public B setCapped(final boolean isCapped) {
232             this.capped = isCapped;
233             return asBuilder();
234         }
235 
236         public B setCollectionName(final String collectionName) {
237             this.collectionName = collectionName;
238             return asBuilder();
239         }
240 
241         public B setCollectionSize(final int collectionSize) {
242             this.collectionSize = collectionSize;
243             return asBuilder();
244         }
245 
246         public B setDatabaseName(final String databaseName) {
247             this.databaseName = databaseName;
248             return asBuilder();
249         }
250 
251         public B setFactoryClassName(final String factoryClassName) {
252             this.factoryClassName = factoryClassName;
253             return asBuilder();
254         }
255 
256         public B setFactoryMethodName(final String factoryMethodName) {
257             this.factoryMethodName = factoryMethodName;
258             return asBuilder();
259         }
260 
261         public B setPassword(final String password) {
262             this.password = password;
263             return asBuilder();
264         }
265 
266         public B setPort(final String port) {
267             this.port = port;
268             return asBuilder();
269         }
270 
271         public B setServer(final String server) {
272             this.server = server;
273             return asBuilder();
274         }
275 
276         public B setUserName(final String userName) {
277             this.userName = userName;
278             return asBuilder();
279         }
280 
281         public B setWriteConcernConstant(final String writeConcernConstant) {
282             this.writeConcernConstant = writeConcernConstant;
283             return asBuilder();
284         }
285 
286         public B setWriteConcernConstantClassName(final String writeConcernConstantClassName) {
287             this.writeConcernConstantClassName = writeConcernConstantClassName;
288             return asBuilder();
289         }
290     }
291 
292     private static final int DEFAULT_COLLECTION_SIZE = 536870912;
293     private static final int DEFAULT_PORT = 27017;
294     private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
295 
296     private static final Logger LOGGER = StatusLogger.getLogger();
297 
298     @PluginBuilderFactory
299     public static <B extends Builder<B>> B newBuilder() {
300         return new Builder<B>().asBuilder();
301     }
302 
303     private final String collectionName;
304     private final Integer collectionSize;
305     private final String description;
306     private final boolean isCapped;
307     private final MongoClient mongoClient;
308     private final MongoDatabase mongoDatabase;
309 
310     private MongoDbProvider(final MongoClient mongoClient, final MongoDatabase mongoDatabase,
311             final String collectionName, final boolean isCapped, final Integer collectionSize,
312             final String description) {
313         this.mongoClient = mongoClient;
314         this.mongoDatabase = mongoDatabase;
315         this.collectionName = collectionName;
316         this.isCapped = isCapped;
317         this.collectionSize = collectionSize;
318         this.description = "mongoDb{ " + description + " }";
319     }
320 
321     @Override
322     public MongoDbConnection getConnection() {
323         return new MongoDbConnection(mongoClient, mongoDatabase, collectionName, isCapped, collectionSize);
324     }
325 
326     @Override
327     public String toString() {
328         return description;
329     }
330 }