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.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.Core;
26  import org.apache.logging.log4j.core.config.plugins.Plugin;
27  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
28  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
29  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
30  import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
31  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
32  import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
33  import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
34  import org.apache.logging.log4j.core.filter.AbstractFilterable;
35  import org.apache.logging.log4j.core.util.NameUtil;
36  import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
37  import org.apache.logging.log4j.status.StatusLogger;
38  import org.apache.logging.log4j.util.LoaderUtil;
39  import org.apache.logging.log4j.util.Strings;
40  
41  import com.mongodb.DB;
42  import com.mongodb.MongoClient;
43  import com.mongodb.MongoCredential;
44  import com.mongodb.ServerAddress;
45  import com.mongodb.WriteConcern;
46  
47  /**
48   * The MongoDB implementation of {@link NoSqlProvider}.
49   */
50  @Plugin(name = "MongoDb", category = Core.CATEGORY_NAME, printObject = true)
51  public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {
52  
53      private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
54      private static final Logger LOGGER = StatusLogger.getLogger();
55      private static final int DEFAULT_PORT = 27017;
56      private static final int DEFAULT_COLLECTION_SIZE = 536870912;
57  
58      private final String collectionName;
59      private final DB database;
60      private final String description;
61      private final WriteConcern writeConcern;
62      private final boolean isCapped;
63      private final Integer collectionSize;
64  
65      private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName,
66              final boolean isCapped, final Integer collectionSize, final String description) {
67          this.database = database;
68          this.writeConcern = writeConcern;
69          this.collectionName = collectionName;
70          this.isCapped = isCapped;
71          this.collectionSize = collectionSize;
72          this.description = "mongoDb{ " + description + " }";
73      }
74  
75      @Override
76      public MongoDbConnection getConnection() {
77          return new MongoDbConnection(this.database, this.writeConcern, this.collectionName, this.isCapped, this.collectionSize);
78      }
79  
80      @Override
81      public String toString() {
82          return this.description;
83      }
84  
85      /**
86       * Factory method for creating a MongoDB provider within the plugin manager.
87       *
88       * @param collectionName The name of the MongoDB collection to which log events should be written.
89       * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to
90       *                             {@link WriteConcern#ACKNOWLEDGED}.
91       * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern
92       *                                      constant. Defaults to {@link WriteConcern}.
93       * @param databaseName The name of the MongoDB database containing the collection to which log events should be
94       *                     written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}.
95       * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with
96       *               {@code factoryClassName&factoryMethodName!=null}.
97       * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually
98       *             exclusive with {@code factoryClassName&factoryMethodName!=null}.
99       * @param userName The username to authenticate against the MongoDB server with.
100      * @param password The password to authenticate against the MongoDB server with.
101      * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a
102      *                         {@link DB} or a {@link MongoClient}.
103      * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory
104      *                          class.
105      * @return a new MongoDB provider.
106      * @deprecated in 2.8; use {@link #newBuilder()} instead.
107      */
108     @PluginFactory
109     public static MongoDbProvider createNoSqlProvider(
110             final String collectionName,
111             final String writeConcernConstant,
112             final String writeConcernConstantClassName,
113             final String databaseName,
114             final String server,
115             final String port,
116             final String userName,
117             final String password,
118             final String factoryClassName,
119 			final String factoryMethodName) {
120     	LOGGER.info("createNoSqlProvider");
121 		return newBuilder().setCollectionName(collectionName).setWriteConcernConstant(writeConcernConstantClassName)
122 				.setWriteConcernConstant(writeConcernConstant).setDatabaseName(databaseName).setServer(server)
123 				.setPort(port).setUserName(userName).setPassword(password).setFactoryClassName(factoryClassName)
124 				.setFactoryMethodName(factoryMethodName).build();
125 	}
126 
127 	@PluginBuilderFactory
128 	public static <B extends Builder<B>> B newBuilder() {
129 		return new Builder<B>().asBuilder();
130 	}
131 
132 	public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
133 			implements org.apache.logging.log4j.core.util.Builder<MongoDbProvider> {
134 
135 		@PluginBuilderAttribute
136 		@ValidHost
137 		private String server = "localhost";
138 
139 		@PluginBuilderAttribute
140 		@ValidPort
141 		private String port = "" + DEFAULT_PORT;
142 
143 		@PluginBuilderAttribute
144 		@Required(message = "No database name provided")
145 		private String databaseName;
146 
147 		@PluginBuilderAttribute
148 		@Required(message = "No collection name provided")
149 		private String collectionName;
150 
151 		@PluginBuilderAttribute
152 		private String userName;
153 
154 		@PluginBuilderAttribute(sensitive = true)
155 		private String password;
156 
157 		@PluginBuilderAttribute("capped")
158 		private boolean isCapped = false;
159 
160 		@PluginBuilderAttribute
161 		private int collectionSize = DEFAULT_COLLECTION_SIZE;
162 
163 		@PluginBuilderAttribute
164 		private String factoryClassName;
165 
166 		@PluginBuilderAttribute
167 		private String factoryMethodName;
168 
169 		@PluginBuilderAttribute
170 		private String writeConcernConstantClassName;
171 
172 		@PluginBuilderAttribute
173 		private String writeConcernConstant;
174 
175 		public B setServer(final String server) {
176 			this.server = server;
177 			return asBuilder();
178 		}
179 
180 		public B setPort(final String port) {
181 			this.port = port;
182 			return asBuilder();
183 		}
184 
185 		public B setDatabaseName(final String databaseName) {
186 			this.databaseName = databaseName;
187 			return asBuilder();
188 		}
189 
190 		public B setCollectionName(final String collectionName) {
191 			this.collectionName = collectionName;
192 			return asBuilder();
193 		}
194 
195 		public B setUserName(final String userName) {
196 			this.userName = userName;
197 			return asBuilder();
198 		}
199 
200 		public B setPassword(final String password) {
201 			this.password = password;
202 			return asBuilder();
203 		}
204 
205 		public B setCapped(final boolean isCapped) {
206 			this.isCapped = isCapped;
207 			return asBuilder();
208 		}
209 
210 		public B setCollectionSize(final int collectionSize) {
211 			this.collectionSize = collectionSize;
212 			return asBuilder();
213 		}
214 
215 		public B setFactoryClassName(final String factoryClassName) {
216 			this.factoryClassName = factoryClassName;
217 			return asBuilder();
218 		}
219 
220 		public B setFactoryMethodName(final String factoryMethodName) {
221 			this.factoryMethodName = factoryMethodName;
222 			return asBuilder();
223 		}
224 
225 		public B setWriteConcernConstantClassName(final String writeConcernConstantClassName) {
226 			this.writeConcernConstantClassName = writeConcernConstantClassName;
227 			return asBuilder();
228 		}
229 
230 		public B setWriteConcernConstant(final String writeConcernConstant) {
231 			this.writeConcernConstant = writeConcernConstant;
232 			return asBuilder();
233 		}
234         
235 		@Override
236 		public MongoDbProvider build() {
237 	        DB database;
238 	        String description;
239 	        if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
240 	            try {
241 	                final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
242 	                final Method method = factoryClass.getMethod(factoryMethodName);
243 	                final Object object = method.invoke(null);
244 
245 	                if (object instanceof DB) {
246 	                    database = (DB) object;
247 	                } else if (object instanceof MongoClient) {
248 	                    if (Strings.isNotEmpty(databaseName)) {
249 	                        database = ((MongoClient) object).getDB(databaseName);
250 	                    } else {
251 	                        LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
252 	                                + "required.", factoryClassName, factoryMethodName);
253 	                        return null;
254 	                    }
255 	                } else if (object == null) {
256 	                    LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName);
257 	                    return null;
258 	                } else {
259 	                    LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName,
260 	                            factoryMethodName, object.getClass().getName());
261 	                    return null;
262 	                }
263 
264 	                description = "database=" + database.getName();
265 	                final List<ServerAddress> addresses = database.getMongo().getAllAddress();
266 	                if (addresses.size() == 1) {
267 	                    description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort();
268 	                } else {
269 	                    description += ", servers=[";
270 	                    for (final ServerAddress address : addresses) {
271 	                        description += " { " + address.getHost() + ", " + address.getPort() + " } ";
272 	                    }
273 	                    description += "]";
274 	                }
275 	            } catch (final ClassNotFoundException e) {
276 	                LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
277 	                return null;
278 	            } catch (final NoSuchMethodException e) {
279 	                LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
280 	                        factoryMethodName, e);
281 	                return null;
282 	            } catch (final Exception e) {
283 	                LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName,
284 	                        e);
285 	                return null;
286 	            }
287 	        } else if (Strings.isNotEmpty(databaseName)) {
288 	            final List<MongoCredential> credentials = new ArrayList<>();
289 	            description = "database=" + databaseName;
290 	            if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
291 	                description += ", username=" + userName + ", passwordHash="
292 	                        + NameUtil.md5(password + MongoDbProvider.class.getName());
293 	                credentials.add(MongoCredential.createCredential(userName, databaseName, password.toCharArray()));
294 	            }
295 	            try {
296 	                final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
297 	                description += ", server=" + server + ", port=" + portInt;
298 	                database = new MongoClient(new ServerAddress(server, portInt), credentials).getDB(databaseName);
299 	            } catch (final Exception e) {
300 	                LOGGER.error(
301 	                        "Failed to obtain a database instance from the MongoClient at server [{}] and " + "port [{}].",
302 	                        server, port);
303 	                return null;
304 	            }
305 	        } else {
306 	            LOGGER.error("No factory method was provided so the database name is required.");
307 	            return null;
308 	        }
309 
310 	        try {
311 	            database.getCollectionNames(); // Check if the database actually requires authentication
312 	        } catch (final Exception e) {
313 	            LOGGER.error(
314 	                    "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
315 	                    e);
316 	            return null;
317 	        }
318 
319 	        final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
320 
321 	        return new MongoDbProvider(database, writeConcern, collectionName, isCapped, collectionSize, description);
322 		}
323 
324 	    private static WriteConcern toWriteConcern(final String writeConcernConstant,
325 	            final String writeConcernConstantClassName) {
326 	        WriteConcern writeConcern;
327 	        if (Strings.isNotEmpty(writeConcernConstant)) {
328 	            if (Strings.isNotEmpty(writeConcernConstantClassName)) {
329 	                try {
330 	                    final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName);
331 	                    final Field field = writeConcernConstantClass.getField(writeConcernConstant);
332 	                    writeConcern = (WriteConcern) field.get(null);
333 	                } catch (final Exception e) {
334 	                    LOGGER.error("Write concern constant [{}.{}] not found, using default.",
335 	                            writeConcernConstantClassName, writeConcernConstant);
336 	                    writeConcern = DEFAULT_WRITE_CONCERN;
337 	                }
338 	            } else {
339 	                writeConcern = WriteConcern.valueOf(writeConcernConstant);
340 	                if (writeConcern == null) {
341 	                    LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
342 	                    writeConcern = DEFAULT_WRITE_CONCERN;
343 	                }
344 	            }
345 	        } else {
346 	            writeConcern = DEFAULT_WRITE_CONCERN;
347 	        }
348 	        return writeConcern;
349 	    }
350     }
351 }