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