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.core.util.NameUtil; 034import org.apache.logging.log4j.status.StatusLogger; 035import org.apache.logging.log4j.util.LoaderUtil; 036import org.apache.logging.log4j.util.Strings; 037import org.bson.codecs.configuration.CodecRegistries; 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}. 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 private static WriteConcern toWriteConcern(final String writeConcernConstant, 056 final String writeConcernConstantClassName) { 057 WriteConcern writeConcern; 058 if (Strings.isNotEmpty(writeConcernConstant)) { 059 if (Strings.isNotEmpty(writeConcernConstantClassName)) { 060 try { 061 final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName); 062 final Field field = writeConcernConstantClass.getField(writeConcernConstant); 063 writeConcern = (WriteConcern) field.get(null); 064 } catch (final Exception e) { 065 LOGGER.error("Write concern constant [{}.{}] not found, using default.", 066 writeConcernConstantClassName, writeConcernConstant); 067 writeConcern = DEFAULT_WRITE_CONCERN; 068 } 069 } else { 070 writeConcern = WriteConcern.valueOf(writeConcernConstant); 071 if (writeConcern == null) { 072 LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant); 073 writeConcern = DEFAULT_WRITE_CONCERN; 074 } 075 } 076 } else { 077 writeConcern = DEFAULT_WRITE_CONCERN; 078 } 079 return writeConcern; 080 } 081 082 @PluginBuilderAttribute 083 @Required(message = "No collection name provided") 084 private String collectionName; 085 086 @PluginBuilderAttribute 087 private int collectionSize = DEFAULT_COLLECTION_SIZE; 088 089 @PluginBuilderAttribute 090 @Required(message = "No database name provided") 091 private String databaseName; 092 093 @PluginBuilderAttribute 094 private String factoryClassName; 095 096 @PluginBuilderAttribute 097 private String factoryMethodName; 098 099 @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}