/* * 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.wp; import com.codahale.metrics.Timer; import com.eightkdata.mongowp.ErrorCode; import com.eightkdata.mongowp.Status; import com.eightkdata.mongowp.bson.utils.DefaultBsonValues; import com.eightkdata.mongowp.exceptions.MongoException; import com.eightkdata.mongowp.messages.request.DeleteMessage; import com.eightkdata.mongowp.messages.request.EmptyBsonContext; import com.eightkdata.mongowp.messages.request.GetMoreMessage; import com.eightkdata.mongowp.messages.request.InsertMessage; import com.eightkdata.mongowp.messages.request.KillCursorsMessage; import com.eightkdata.mongowp.messages.request.UpdateMessage; import com.eightkdata.mongowp.messages.response.ReplyMessage; import com.eightkdata.mongowp.messages.utils.IterableDocumentProvider; import com.eightkdata.mongowp.server.api.Command; import com.eightkdata.mongowp.server.api.CommandsLibrary; import com.eightkdata.mongowp.server.api.Request; import com.eightkdata.mongowp.server.api.SafeRequestProcessor; import com.eightkdata.mongowp.server.api.pojos.QueryRequest; import com.google.common.collect.Lists; import com.torodb.core.retrier.Retrier; import com.torodb.core.retrier.RetrierGiveUpException; import com.torodb.mongodb.commands.TorodbCommandsLibrary; import com.torodb.mongodb.commands.TorodbCommandsLibrary.RequiredTransaction; import com.torodb.mongodb.commands.signatures.general.FindCommand; import com.torodb.mongodb.commands.signatures.general.FindCommand.FindArgument; import com.torodb.mongodb.commands.signatures.general.FindCommand.FindResult; import com.torodb.mongodb.core.ExclusiveWriteMongodTransaction; import com.torodb.mongodb.core.MongodConnection; import com.torodb.mongodb.core.MongodMetrics; import com.torodb.mongodb.core.MongodServer; import com.torodb.mongodb.core.ReadOnlyMongodTransaction; import com.torodb.mongodb.core.WriteMongodTransaction; import io.netty.util.AttributeKey; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.Callable; import javax.inject.Inject; import javax.inject.Singleton; /** * */ @Singleton public class TorodbSafeRequestProcessor implements SafeRequestProcessor<MongodConnection> { private static final Logger LOGGER = LogManager.getLogger(TorodbSafeRequestProcessor.class); private final MongodServer server; public static final AttributeKey<MongodConnection> MONGOD_CONNECTION_KEY = AttributeKey .newInstance("mongod.connection"); private final Retrier retrier; private final TorodbCommandsLibrary commandsLibrary; private final MongodMetrics mongodMetrics; @Inject public TorodbSafeRequestProcessor(MongodServer server, Retrier retrier, TorodbCommandsLibrary commandsLibrary, MongodMetrics mongodMetrics) { this.server = server; this.retrier = retrier; this.commandsLibrary = commandsLibrary; this.mongodMetrics = mongodMetrics; } @Override public MongodConnection openConnection() { MongodConnection connection = server.openConnection(); LOGGER.info("Accepted connection {}", connection.getConnectionId()); return connection; } @Override public <A, R> Status<R> execute(Request req, Command<? super A, ? super R> command, A arg, MongodConnection connection) { mongodMetrics.getCommands().mark(); Timer timer = mongodMetrics.getTimer(command); try (Timer.Context ctx = timer.time()) { Callable<Status<R>> callable; RequiredTransaction commandType = commandsLibrary.getCommandType(command); switch (commandType) { case NO_TRANSACTION: callable = () -> { return connection.getCommandsExecutor().execute(req, command, arg, connection); }; break; case READ_TRANSACTION: callable = () -> { try (ReadOnlyMongodTransaction trans = connection.openReadOnlyTransaction()) { return trans.execute(req, command, arg); } }; break; case WRITE_TRANSACTION: callable = () -> { try (WriteMongodTransaction trans = connection.openWriteTransaction(true)) { Status<R> result = trans.execute(req, command, arg); if (result.isOk()) { trans.commit(); } return result; } }; break; case EXCLUSIVE_WRITE_TRANSACTION: callable = () -> { try (ExclusiveWriteMongodTransaction trans = connection.openExclusiveWriteTransaction( true)) { Status<R> result = trans.execute(req, command, arg); if (result.isOk()) { trans.commit(); } return result; } }; break; default: throw new AssertionError("Unexpected command type" + commandType); } try { return retrier.retry(callable); } catch (RetrierGiveUpException ex) { return Status.from( ErrorCode.CONFLICTING_OPERATION_IN_PROGRESS, "It was impossible to execute " + command.getCommandName() + " after several attempts" ); } } } @Override public CommandsLibrary getCommandsLibrary() { return commandsLibrary; } @Override public ReplyMessage query(MongodConnection connection, Request req, int requestId, QueryRequest queryRequest) throws MongoException { FindArgument findArg = new FindArgument.Builder() .setCollection(queryRequest.getCollection()) .setFilter(queryRequest.getQuery() != null ? queryRequest.getQuery() : DefaultBsonValues.EMPTY_DOC) .build(); Status<FindResult> status = execute(req, FindCommand.INSTANCE, findArg, connection); if (!status.isOk()) { throw new MongoException(status.getErrorCode(), status.getErrorMsg()); } FindResult result = status.getResult(); assert result != null; return new ReplyMessage( EmptyBsonContext.getInstance(), requestId, false, false, false, false, result.getCursor().getCursorId(), queryRequest.getNumberToSkip(), IterableDocumentProvider.of(Lists.newArrayList(result.getCursor().getFirstBatch())) ); } @Override public ReplyMessage getMore(MongodConnection connection, Request req, int requestId, GetMoreMessage moreMessage) throws MongoException { throw new UnsupportedOperationException("Not supported yet."); } @Override public void killCursors(MongodConnection connection, Request req, KillCursorsMessage killCursorsMessage) throws MongoException { throw new UnsupportedOperationException("Not supported yet."); } @Override public void insert(MongodConnection connection, Request req, InsertMessage insertMessage) throws MongoException { throw new UnsupportedOperationException("Not supported yet."); } @Override public void update(MongodConnection connection, Request req, UpdateMessage updateMessage) throws MongoException { throw new UnsupportedOperationException("Not supported yet."); } @Override public void delete(MongodConnection connection, Request req, DeleteMessage deleteMessage) throws MongoException { throw new UnsupportedOperationException("Not supported yet."); } }