/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.mongodb.commands.signatures.repl;
import com.eightkdata.mongowp.bson.BsonArray;
import com.eightkdata.mongowp.bson.BsonDocument;
import com.eightkdata.mongowp.bson.BsonValue;
import com.eightkdata.mongowp.exceptions.BadValueException;
import com.eightkdata.mongowp.exceptions.MongoException;
import com.eightkdata.mongowp.exceptions.NoSuchKeyException;
import com.eightkdata.mongowp.exceptions.TypesMismatchException;
import com.eightkdata.mongowp.fields.ArrayField;
import com.eightkdata.mongowp.fields.DocField;
import com.eightkdata.mongowp.fields.IntField;
import com.eightkdata.mongowp.fields.StringField;
import com.eightkdata.mongowp.server.api.impl.AbstractNotAliasableCommand;
import com.eightkdata.mongowp.server.api.oplog.OplogOperation;
import com.eightkdata.mongowp.utils.BsonArrayBuilder;
import com.eightkdata.mongowp.utils.BsonDocumentBuilder;
import com.eightkdata.mongowp.utils.BsonReaderTool;
import com.google.common.collect.ImmutableList;
import com.torodb.mongodb.commands.pojos.OplogOperationParser;
import com.torodb.mongodb.commands.signatures.repl.ApplyOpsCommand.ApplyOpsArgument;
import com.torodb.mongodb.commands.signatures.repl.ApplyOpsCommand.ApplyOpsReply;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
public class ApplyOpsCommand extends AbstractNotAliasableCommand<ApplyOpsArgument, ApplyOpsReply> {
public static final ApplyOpsCommand INSTANCE = new ApplyOpsCommand();
private ApplyOpsCommand() {
super("applyOps");
}
@Override
public boolean isSlaveOk() {
return false;
}
@Override
public Class<? extends ApplyOpsArgument> getArgClass() {
return ApplyOpsArgument.class;
}
@Override
public ApplyOpsArgument unmarshallArg(BsonDocument requestDoc)
throws BadValueException, TypesMismatchException, NoSuchKeyException {
return ApplyOpsArgument.unmarshall(requestDoc);
}
@Override
public BsonDocument marshallArg(ApplyOpsArgument request) {
throw new UnsupportedOperationException("Not supported yet."); //TODO
}
@Override
public Class<? extends ApplyOpsReply> getResultClass() {
return ApplyOpsReply.class;
}
@Override
public BsonDocument marshallResult(ApplyOpsReply reply) {
return reply.marshall();
}
@Override
public ApplyOpsReply unmarshallResult(BsonDocument resultDoc) throws
MongoException, UnsupportedOperationException {
throw new UnsupportedOperationException("Not supported yet."); //TODO
}
@Immutable
public static class ApplyOpsArgument {
private final boolean alwaysUpsert;
private final ImmutableList<OplogOperation> operations;
private final ImmutableList<Precondition> preconditions;
public ApplyOpsArgument(
boolean alwaysUpsert,
ImmutableList<OplogOperation> operations,
ImmutableList<Precondition> preconditions) {
this.alwaysUpsert = alwaysUpsert;
this.operations = operations;
this.preconditions = preconditions;
}
public boolean isAlwaysUpsert() {
return alwaysUpsert;
}
public ImmutableList<OplogOperation> getOperations() {
return operations;
}
public ImmutableList<Precondition> getPreconditions() {
return preconditions;
}
private static ApplyOpsArgument unmarshall(BsonDocument requestDoc)
throws BadValueException, TypesMismatchException, NoSuchKeyException {
final String commandName = "applyOps";
if (!requestDoc.containsKey(commandName) || !requestDoc.get(commandName).isArray()) {
throw new BadValueException("ops has to be an array");
}
boolean alwaysUpsert = BsonReaderTool.getBoolean(requestDoc, "alwaysUpsert", true);
BsonArray opsArray = requestDoc.get(commandName).asArray();
ImmutableList.Builder<OplogOperation> ops = ImmutableList.builder();
for (BsonValue uncastedOp : opsArray) {
ops.add(OplogOperationParser.fromBson(uncastedOp));
}
ImmutableList.Builder<Precondition> preconditions = ImmutableList.builder();
if (requestDoc.containsKey("preCondition") && requestDoc.get("preCondition").isArray()) {
for (BsonValue uncastedPrecondition : requestDoc.get("preCondition").asArray()) {
preconditions.add(Precondition.fromBson(uncastedPrecondition));
}
}
return new ApplyOpsArgument(alwaysUpsert, ops.build(), preconditions.build());
}
public static class Precondition {
private final String namespace;
private final BsonDocument query;
private final BsonDocument restriction;
public Precondition(String namespace, BsonDocument query, BsonDocument restriction) {
this.namespace = namespace;
this.query = query;
this.restriction = restriction;
}
public String getNamespace() {
return namespace;
}
public BsonDocument getQuery() {
return query;
}
public BsonDocument getRestriction() {
return restriction;
}
private static Precondition fromBson(BsonValue uncastedPrecondition) throws BadValueException,
TypesMismatchException, NoSuchKeyException {
if (!uncastedPrecondition.isDocument()) {
throw new BadValueException("applyOps preconditions must "
+ "be an array of documents, but one of their "
+ "elements has the non document value '" + uncastedPrecondition);
}
BsonDocument preconditionDoc = uncastedPrecondition.asDocument();
String namespace = BsonReaderTool.getString(preconditionDoc, "ns");
BsonDocument query = BsonReaderTool.getDocument(preconditionDoc, "q");
BsonDocument req = BsonReaderTool.getDocument(preconditionDoc, "res");
return new Precondition(namespace, query, req);
}
}
}
@Immutable
public static class ApplyOpsReply {
private static final DocField GOT_FIELD = new DocField("got");
private static final DocField WHAT_FAILED_FIELD = new DocField("whatFailed");
private static final StringField ERRMSG_FIELD = new StringField("errmsg");
private static final IntField APPLIED_FIELD = new IntField("applied");
private static final ArrayField RESULT_FIELD = new ArrayField("result");
private final int num;
private final ImmutableList<Boolean> results;
private final BsonDocument got;
private final BsonDocument whatFailed;
private ApplyOpsReply(
BsonDocument got,
BsonDocument whatFailed) {
this.got = got;
this.whatFailed = whatFailed;
this.num = 0;
this.results = ImmutableList.of();
}
public ApplyOpsReply(int num, ImmutableList<Boolean> results) {
this.num = num;
this.results = results;
this.got = null;
this.whatFailed = null;
}
public int getNum() {
return num;
}
public ImmutableList<Boolean> getResults() {
return results;
}
@Nullable
public BsonDocument getGot() {
return got;
}
@Nullable
public BsonDocument getWhatFailed() {
return whatFailed;
}
private BsonDocument marshall() {
BsonDocumentBuilder builder = new BsonDocumentBuilder();
if (got != null) {
builder.append(GOT_FIELD, got)
.append(WHAT_FAILED_FIELD, whatFailed)
.append(ERRMSG_FIELD, "pre-condition failed");
} else {
builder.append(APPLIED_FIELD, getNum());
BsonArrayBuilder bsonResult = new BsonArrayBuilder();
for (Boolean iestResult : results) {
bsonResult.add(iestResult);
}
builder.append(RESULT_FIELD, bsonResult.build());
}
return builder.build();
}
}
}