/*
* Copyright 2010 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.flume;
import com.cloudera.flume.conf.Context;
import com.cloudera.flume.conf.SinkFactory.SinkBuilder;
import com.cloudera.flume.core.Event;
import com.cloudera.flume.core.EventSink;
import com.cloudera.util.Pair;
import com.google.common.base.Preconditions;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.MongoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
// Using SLF4J per https://issues.cloudera.org/browse/FLUME-309
public class MongoDBSink extends EventSink.Base {
private static final Logger LOG = LoggerFactory.getLogger(MongoDBSink.class);
private final MongoClientURI uri;
private MongoClient mongo;
private DBCollection collection;
/**
* Constructs a new instance against the given URI
* @param uriString the MongoDB URI
*/
public MongoDBSink(final String uriString) {
uri = new MongoClientURI(uriString);
}
@Override
public void open() {
try {
mongo = new MongoClient(uri);
} catch (final Exception e) {
LOG.error("Connecting to MongoDB failed.", e);
throw new MongoException("Failed to connect to MongoDB. ", e);
}
try {
collection = mongo.getDB(uri.getDatabase()).getCollection(uri.getCollection());
} catch (final Exception e) {
LOG.error("Connected to MongoDB but failed in acquiring collection.", e);
throw new MongoException("Could not acquire specified collection.", e);
}
}
@Override
public void append(final Event e) throws IOException {
/*
* TODO - Performance would be best if we wrote directly to BSON here...
* e.g. Not double converting the timestamp, and skipping string
* encoding/decoding the message body
*/
// Would it work to use Timestamp + Nanos + Hostname as the ID or is
// there still a collision chance?
BasicDBObjectBuilder b = BasicDBObjectBuilder.start("timestamp", new Date(e.getTimestamp()));
b.append("nanoseconds", e.getNanos());
b.append("hostname", e.getHost());
b.append("priority", e.getPriority().name());
b.append("message", new String(e.getBody()));
b.append("metadata", new BasicDBObject(e.getAttrs()));
collection.insert(b.get());
}
@Override
public void close() throws IOException {
// TODO - Flume docs specify all blocking must be kicked during
// disconnect. Verify we do. No hanging!
mongo.close();
}
public static SinkBuilder builder() {
return new SinkBuilder() {
// Create a new sink using a MongoDB URI
@Override
public EventSink build(final Context context, final String... argv) {
Preconditions
.checkArgument(argv.length == 1,
"usage: mongoDBSink(\"mongodb://[username:password@]host1[:port1][,host2[:port2],...[,"
+ "hostN[:portN]]][/[database][?options]]\")\n ... See "
+ "http://www.mongodb.org/display/DOCS/Connections for information on the MongoDB Connection"
+ " URI Format."
+ "\n\t Note that using [?options] you can specify Write Concern related settings: "
+ "\n\t\t safe={true|false} (default: false) Whether or not the driver should send getLastError to "
+ "verify each write operation."
+ "\n\t\t w={n} (default: 0) Specify the number of servers to replicate a write to before returning"
+ " success. When non-zero, implies safe=true."
+ "\n\t\t wtimeout={ms} (default: wait forever) The number of milliseconds to wait for W "
+ "replications to complete. When non-zero, implies safe=true."
+ "\n\t\t fsync={true|false} (default: false) When enabled, "
+ "forces an fsync after each write operation to increase durability. You probably *don't* want to "
+ "do this; see the MongoDB docs for info. When 'true', implies safe=true"
);
return new MongoDBSink(argv[0]);
}
};
}
public com.mongodb.MongoClientURI getUri() {
return uri;
}
public com.mongodb.MongoClient getMongo() {
return mongo;
}
public com.mongodb.DBCollection getCollection() {
return collection;
}
// Special function used by Flume's SourceFactory to pull this class in
// and use it as a plugin Sink
public static List<Pair<String, SinkBuilder>> getSinkBuilders() {
List<Pair<String, SinkBuilder>> builders = new ArrayList<Pair<String, SinkBuilder>>();
builders.add(new Pair<String, SinkBuilder>("mongoDBSink", builder()));
return builders;
}
}