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 */
017 package org.apache.logging.log4j.nosql.appender.mongodb;
018
019 import java.lang.reflect.Field;
020 import java.lang.reflect.Method;
021 import java.util.List;
022
023 import com.mongodb.DB;
024 import com.mongodb.MongoClient;
025 import com.mongodb.ServerAddress;
026 import com.mongodb.WriteConcern;
027 import org.apache.logging.log4j.Logger;
028 import org.apache.logging.log4j.core.appender.AbstractAppender;
029 import org.apache.logging.log4j.core.config.plugins.Plugin;
030 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
031 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
032 import org.apache.logging.log4j.core.util.Loader;
033 import org.apache.logging.log4j.core.util.NameUtil;
034 import org.apache.logging.log4j.nosql.appender.NoSqlProvider;
035 import org.apache.logging.log4j.status.StatusLogger;
036
037 /**
038 * The MongoDB implementation of {@link NoSqlProvider}.
039 */
040 @Plugin(name = "MongoDb", category = "Core", printObject = true)
041 public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {
042 private static final Logger LOGGER = StatusLogger.getLogger();
043
044 private final String collectionName;
045 private final DB database;
046 private final String description;
047
048 private final WriteConcern writeConcern;
049
050 private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName,
051 final String description) {
052 this.database = database;
053 this.writeConcern = writeConcern;
054 this.collectionName = collectionName;
055 this.description = "mongoDb{ " + description + " }";
056 }
057
058 @Override
059 public MongoDbConnection getConnection() {
060 return new MongoDbConnection(this.database, this.writeConcern, this.collectionName);
061 }
062
063 @Override
064 public String toString() {
065 return this.description;
066 }
067
068 /**
069 * Factory method for creating a MongoDB provider within the plugin manager.
070 *
071 * @param collectionName The name of the MongoDB collection to which log events should be written.
072 * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to
073 * {@link WriteConcern#ACKNOWLEDGED}.
074 * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern
075 * constant. Defaults to {@link WriteConcern}.
076 * @param databaseName The name of the MongoDB database containing the collection to which log events should be
077 * written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}.
078 * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with
079 * {@code factoryClassName&factoryMethodName!=null}.
080 * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually
081 * exclusive with {@code factoryClassName&factoryMethodName!=null}.
082 * @param username The username to authenticate against the MongoDB server with.
083 * @param password The password to authenticate against the MongoDB server with.
084 * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a
085 * {@link DB} or a {@link MongoClient}.
086 * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory
087 * class.
088 * @return a new MongoDB provider.
089 */
090 @PluginFactory
091 public static MongoDbProvider createNoSqlProvider(
092 @PluginAttribute("collectionName") final String collectionName,
093 @PluginAttribute("writeConcernConstant") final String writeConcernConstant,
094 @PluginAttribute("writeConcernConstantClass") final String writeConcernConstantClassName,
095 @PluginAttribute("databaseName") final String databaseName,
096 @PluginAttribute("server") final String server,
097 @PluginAttribute("port") final String port,
098 @PluginAttribute("username") final String username,
099 @PluginAttribute(value = "password", sensitive = true) final String password,
100 @PluginAttribute("factoryClassName") final String factoryClassName,
101 @PluginAttribute("factoryMethodName") final String factoryMethodName) {
102 DB database;
103 String description;
104 if (factoryClassName != null && factoryClassName.length() > 0 &&
105 factoryMethodName != null && factoryMethodName.length() > 0) {
106 try {
107 final Class<?> factoryClass = Loader.loadClass(factoryClassName);
108 final Method method = factoryClass.getMethod(factoryMethodName);
109 final Object object = method.invoke(null);
110
111 if (object instanceof DB) {
112 database = (DB) object;
113 } else if (object instanceof MongoClient) {
114 if (databaseName != null && databaseName.length() > 0) {
115 database = ((MongoClient) object).getDB(databaseName);
116 } else {
117 LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
118 + "required.", factoryClassName, factoryMethodName);
119 return null;
120 }
121 } else if (object == null) {
122 LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName);
123 return null;
124 } else {
125 LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName,
126 factoryMethodName, object.getClass().getName());
127 return null;
128 }
129
130 description = "database=" + database.getName();
131 final List<ServerAddress> addresses = database.getMongo().getAllAddress();
132 if (addresses.size() == 1) {
133 description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort();
134 } else {
135 description += ", servers=[";
136 for (final ServerAddress address : addresses) {
137 description += " { " + address.getHost() + ", " + address.getPort() + " } ";
138 }
139 description += "]";
140 }
141 } catch (final ClassNotFoundException e) {
142 LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
143 return null;
144 } catch (final NoSuchMethodException e) {
145 LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
146 factoryMethodName, e);
147 return null;
148 } catch (final Exception e) {
149 LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName,
150 e);
151 return null;
152 }
153 } else if (databaseName != null && databaseName.length() > 0) {
154 description = "database=" + databaseName;
155 try {
156 if (server != null && server.length() > 0) {
157 final int portInt = AbstractAppender.parseInt(port, 0);
158 description += ", server=" + server;
159 if (portInt > 0) {
160 description += ", port=" + portInt;
161 database = new MongoClient(server, portInt).getDB(databaseName);
162 } else {
163 database = new MongoClient(server).getDB(databaseName);
164 }
165 } else {
166 database = new MongoClient().getDB(databaseName);
167 }
168 } catch (final Exception e) {
169 LOGGER.error("Failed to obtain a database instance from the MongoClient at server [{}] and "
170 + "port [{}].", server, port);
171 return null;
172 }
173 } else {
174 LOGGER.error("No factory method was provided so the database name is required.");
175 return null;
176 }
177
178 if (!database.isAuthenticated()) {
179 if (username != null && username.length() > 0 && password != null && password.length() > 0) {
180 description += ", username=" + username + ", passwordHash="
181 + NameUtil.md5(password + MongoDbProvider.class.getName());
182 MongoDbConnection.authenticate(database, username, password);
183 } else {
184 LOGGER.error("The database is not already authenticated so you must supply a username and password "
185 + "for the MongoDB provider.");
186 return null;
187 }
188 }
189
190 WriteConcern writeConcern;
191 if (writeConcernConstant != null && writeConcernConstant.length() > 0) {
192 if (writeConcernConstantClassName != null && writeConcernConstantClassName.length() > 0) {
193 try {
194 final Class<?> writeConcernConstantClass = Loader.loadClass(writeConcernConstantClassName);
195 final Field field = writeConcernConstantClass.getField(writeConcernConstant);
196 writeConcern = (WriteConcern) field.get(null);
197 } catch (final Exception e) {
198 LOGGER.error("Write concern constant [{}.{}] not found, using default.",
199 writeConcernConstantClassName, writeConcernConstant);
200 writeConcern = WriteConcern.ACKNOWLEDGED;
201 }
202 } else {
203 writeConcern = WriteConcern.valueOf(writeConcernConstant);
204 if (writeConcern == null) {
205 LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
206 writeConcern = WriteConcern.ACKNOWLEDGED;
207 }
208 }
209 } else {
210 writeConcern = WriteConcern.ACKNOWLEDGED;
211 }
212
213 return new MongoDbProvider(database, writeConcern, collectionName, description);
214 }
215 }