package org.togglz.mongodb; import com.mongodb.MongoClient; import com.mongodb.WriteConcern; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.UpdateOptions; import org.bson.Document; import org.togglz.core.Feature; import org.togglz.core.repository.FeatureState; import org.togglz.core.repository.StateRepository; /** * <p> * A state repository which stores the feature state in a MongoDB database. * </p> * * <p> * The class provides a builder which can be used to configure the repository: * </p> * * <pre> * StateRepository repository = MongoStateRepository * .newBuilder(mongoClient, "mydb") * .collection("togglz") * .writeConcern(WriteConcern.REPLICA_ACKNOWLEDGED) * .build(); * </pre> * * @author Christian Kaltepoth */ public class MongoStateRepository implements StateRepository { protected static final String FIELD_FEATURE = "feature"; protected static final String FIELD_ENABLED = "enabled"; protected static final String FIELD_STRATEGY = "strategy"; protected static final String FIELD_PARAMS = "params"; protected final MongoClient mongoClient; protected final String dbname; protected final String collection; protected final WriteConcern writeConcern; private MongoStateRepository(Builder builder) { this.mongoClient = builder.client; this.dbname = builder.dbname; this.collection = builder.collection; this.writeConcern = builder.writeConcern; } @Override public FeatureState getFeatureState(Feature feature) { Document result = (Document) togglzCollection().find(queryFor(feature)).first(); if (result != null) { FeatureState state = new FeatureState(feature); boolean enabledValue = result.getBoolean(FIELD_ENABLED, false); state.setEnabled(enabledValue); String strategyValue = result.getString(FIELD_STRATEGY); if (strategyValue != null) { state.setStrategyId(strategyValue.trim()); } Object paramsValue = result.get(FIELD_PARAMS); if (paramsValue instanceof Document) { Document params = (Document) paramsValue; for (String key : params.keySet()) { state.setParameter(key, params.get(key).toString().trim()); } } return state; } return null; } @Override public void setFeatureState(FeatureState featureState) { Document featureStateDocument = new Document() .append(FIELD_FEATURE, featureState.getFeature().name()) .append(FIELD_ENABLED, featureState.isEnabled()); if (featureState.getStrategyId() != null) { featureStateDocument.append(FIELD_STRATEGY, featureState.getStrategyId()); } if (featureState.getParameterNames().size() > 0) { Document params = new Document(); for (String key : featureState.getParameterNames()) { params.append(key, featureState.getParameter(key)); } featureStateDocument.append(FIELD_PARAMS, params); } Document query = queryFor(featureState.getFeature()); UpdateOptions updateOptions = new UpdateOptions().upsert(true); togglzCollection().replaceOne(query, featureStateDocument, updateOptions); } protected Document queryFor(Feature feature) { return new Document(FIELD_FEATURE, feature.name()); } protected MongoCollection togglzCollection() { MongoDatabase db = mongoClient.getDatabase(dbname); return db.getCollection(collection).withWriteConcern(writeConcern); } /** * Creates a new builder for creating a {@link MongoStateRepository}. * * @param client the client instance to use for connecting to MongoDB * @param dbname the database name used for storing features state. */ public static Builder newBuilder(MongoClient client, String dbname) { return new Builder(client, dbname); } /** * Builder for a {@link MongoStateRepository}. */ public static class Builder { private final MongoClient client; private final String dbname; private String collection = "togglz"; private WriteConcern writeConcern = WriteConcern.ACKNOWLEDGED; /** * Creates a new builder for a {@link MongoStateRepository}. * * @param client the client instance to use for connecting to MongoDB * @param dbname the database name used for storing features state. */ public Builder(MongoClient client, String dbname) { this.client = client; this.dbname = dbname; } /** * The name of the collection used by the repository to store the feature state. The default is <code>togglz</code>. * * @param collection The name of the collection to use */ public Builder collection(String collection) { this.collection = collection; return this; } /** * The {@link WriteConcern} used when accessing the database. By default <code>ACKNOWLEDGED</code> will be used. * * @param writeConcern The {@link WriteConcern} to use * @return */ public Builder writeConcern(WriteConcern writeConcern) { this.writeConcern = writeConcern; return this; } /** * Creates a new {@link MongoStateRepository} using the current settings. */ public MongoStateRepository build() { return new MongoStateRepository(this); } } }