001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.mongodb3; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Method; 021 022import org.apache.logging.log4j.Logger; 023import org.apache.logging.log4j.core.Core; 024import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider; 025import org.apache.logging.log4j.core.config.plugins.Plugin; 026import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 027import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 028import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; 029import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 030import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost; 031import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; 032import org.apache.logging.log4j.core.filter.AbstractFilterable; 033import org.apache.logging.log4j.status.StatusLogger; 034import org.apache.logging.log4j.util.LoaderUtil; 035import org.apache.logging.log4j.util.Strings; 036import org.bson.codecs.configuration.CodecRegistries; 037import org.bson.codecs.configuration.CodecRegistry; 038 039import com.mongodb.MongoClient; 040import com.mongodb.MongoClientOptions; 041import com.mongodb.MongoCredential; 042import com.mongodb.ServerAddress; 043import com.mongodb.WriteConcern; 044import com.mongodb.client.MongoDatabase; 045 046/** 047 * The MongoDB implementation of {@link NoSqlProvider}.using the MongoDB driver version 3 API. 048 */ 049@Plugin(name = "MongoDb3", category = Core.CATEGORY_NAME, printObject = true) 050public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> { 051 052 public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B> 053 implements org.apache.logging.log4j.core.util.Builder<MongoDbProvider> { 054 055 // @formatter:off 056 private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries( 057 CodecRegistries.fromCodecs(LevelCodec.INSTANCE), 058 MongoClient.getDefaultCodecRegistry()); 059 // @formatter:on 060 061 private static WriteConcern toWriteConcern(final String writeConcernConstant, 062 final String writeConcernConstantClassName) { 063 WriteConcern writeConcern; 064 if (Strings.isNotEmpty(writeConcernConstant)) { 065 if (Strings.isNotEmpty(writeConcernConstantClassName)) { 066 try { 067 final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName); 068 final Field field = writeConcernConstantClass.getField(writeConcernConstant); 069 writeConcern = (WriteConcern) field.get(null); 070 } catch (final Exception e) { 071 LOGGER.error("Write concern constant [{}.{}] not found, using default.", 072 writeConcernConstantClassName, writeConcernConstant); 073 writeConcern = DEFAULT_WRITE_CONCERN; 074 } 075 } else { 076 writeConcern = WriteConcern.valueOf(writeConcernConstant); 077 if (writeConcern == null) { 078 LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant); 079 writeConcern = DEFAULT_WRITE_CONCERN; 080 } 081 } 082 } else { 083 writeConcern = DEFAULT_WRITE_CONCERN; 084 } 085 return writeConcern; 086 } 087 088 @PluginBuilderAttribute 089 @Required(message = "No collection name provided") 090 private String collectionName; 091 092 @PluginBuilderAttribute 093 private int collectionSize = DEFAULT_COLLECTION_SIZE; 094 095 @PluginBuilderAttribute 096 @Required(message = "No database name provided") 097 private String databaseName; 098 099 @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}