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