1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
56 private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries(
57 CodecRegistries.fromCodecs(LevelCodec.INSTANCE),
58 MongoClient.getDefaultCodecRegistry());
59
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
188 final MongoClientOptions options = MongoClientOptions.builder()
189 .codecRegistry(CODEC_REGISTRIES)
190 .writeConcern(writeConcern)
191 .build();
192
193 final ServerAddress serverAddress = new ServerAddress(server, portInt);
194 mongoClient = mongoCredential == null ?
195
196 new MongoClient(serverAddress, options) :
197 new MongoClient(serverAddress, mongoCredential, options);
198
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();
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 }