/*
* RESTHeart - the Web API for MongoDB
* Copyright (C) SoftInstigate Srl
*
* 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 org.restheart.handlers;
import org.restheart.handlers.metadata.CheckHandler;
import org.restheart.handlers.root.GetRootHandler;
import org.restheart.handlers.collection.DeleteCollectionHandler;
import org.restheart.handlers.collection.GetCollectionHandler;
import org.restheart.handlers.collection.PatchCollectionHandler;
import org.restheart.handlers.collection.PostCollectionHandler;
import org.restheart.handlers.collection.PutCollectionHandler;
import org.restheart.handlers.database.DeleteDBHandler;
import org.restheart.handlers.database.GetDBHandler;
import org.restheart.handlers.database.PatchDBHandler;
import org.restheart.handlers.database.PutDBHandler;
import org.restheart.handlers.document.DeleteDocumentHandler;
import org.restheart.handlers.document.GetDocumentHandler;
import org.restheart.handlers.document.PatchDocumentHandler;
import org.restheart.handlers.document.PutDocumentHandler;
import org.restheart.handlers.indexes.DeleteIndexHandler;
import org.restheart.handlers.indexes.GetIndexesHandler;
import org.restheart.handlers.indexes.PutIndexHandler;
import org.restheart.utils.HttpStatus;
import io.undertow.server.HttpServerExchange;
import java.util.HashMap;
import java.util.Map;
import org.restheart.metadata.transformers.PlainJsonTransformer;
import org.restheart.handlers.schema.JsonMetaSchemaChecker;
import static org.restheart.metadata.transformers.RepresentationTransformer.PHASE;
import static org.restheart.handlers.RequestContext.METHOD;
import static org.restheart.handlers.RequestContext.TYPE;
import org.restheart.handlers.aggregation.AggregationTransformer;
import org.restheart.handlers.files.DeleteBucketHandler;
import org.restheart.handlers.files.DeleteFileHandler;
import org.restheart.handlers.files.GetFileBinaryHandler;
import org.restheart.handlers.files.GetFileHandler;
import org.restheart.handlers.files.PostBucketHandler;
import org.restheart.handlers.files.PutBucketHandler;
import org.restheart.handlers.metadata.ResponseTransformerMetadataHandler;
import org.restheart.handlers.metadata.BeforeWriteCheckMetadataHandler;
import org.restheart.handlers.metadata.RequestTransformerMetadataHandler;
import org.restheart.handlers.aggregation.GetAggregationHandler;
import org.restheart.handlers.bulk.BulkDeleteDocumentsHandler;
import org.restheart.handlers.bulk.BulkPatchDocumentsHandler;
import org.restheart.handlers.bulk.BulkPostCollectionHandler;
import org.restheart.handlers.files.PutFileHandler;
import org.restheart.handlers.metadata.AfterWriteCheckMetadataHandler;
import org.restheart.handlers.schema.JsonSchemaTransformer;
import org.restheart.handlers.metadata.TransformerHandler;
import org.restheart.handlers.metadata.HookMetadataHandler;
import org.restheart.utils.ResponseHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Andrea Di Cesare {@literal <andrea@softinstigate.com>}
*/
public final class RequestDispacherHandler extends PipedHttpHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestDispacherHandler.class);
private final Map<TYPE, Map<METHOD, PipedHttpHandler>> handlersMultimap;
private final ResponseSenderHandler responseSenderHandler
= new ResponseSenderHandler(null);
/**
* Creates a new instance of RequestDispacherHandler
*/
public RequestDispacherHandler() {
this(true);
}
/**
* Used for testing. By passing a <code>false</code> parameter then handlers
* are not initialized and you can put your own (e.g. mocks)
*
* @param initialize if false then do not initialize the handlersMultimap
*/
RequestDispacherHandler(boolean initialize) {
super(null, null);
this.handlersMultimap = new HashMap<>();
if (initialize) {
defaultInit();
}
}
/**
* Put into handlersMultimap all the default combinations of types, methods
* and PipedHttpHandler objects
*/
protected void defaultInit() {
LOGGER.trace("Initialize default HTTP handlers:");
// *** ROOT handlers
putPipedHttpHandler(TYPE.ROOT, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetRootHandler(
new TransformerHandler(
new ResponseSenderHandler(null),
PHASE.RESPONSE,
new PlainJsonTransformer()))));
// *** DB handlers
putPipedHttpHandler(TYPE.DB, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetDBHandler(
new TransformerHandler(
new ResponseTransformerMetadataHandler(
new ResponseSenderHandler(null)),
PHASE.RESPONSE,
new AggregationTransformer(),
new PlainJsonTransformer()))));
putPipedHttpHandler(TYPE.DB, METHOD.PUT,
new RequestTransformerMetadataHandler(
new RequestTransformerMetadataHandler(
new PutDBHandler(
respTransformers()))));
putPipedHttpHandler(TYPE.DB, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteDBHandler(
respTransformers())));
putPipedHttpHandler(TYPE.DB, METHOD.PATCH,
new RequestTransformerMetadataHandler(
new PatchDBHandler(
respTransformers())));
// *** COLLECTION handlers
putPipedHttpHandler(TYPE.COLLECTION, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetCollectionHandler(
new ResponseTransformerMetadataHandler(
new TransformerHandler(
new HookMetadataHandler(
new ResponseSenderHandler()),
PHASE.RESPONSE,
new PlainJsonTransformer(),
new AggregationTransformer())))));
putPipedHttpHandler(TYPE.COLLECTION, METHOD.POST,
new NormalOrBulkDispatcherHandler(
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new PostCollectionHandler(
new AfterWriteCheckMetadataHandler(
respTransformers())))),
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new BulkPostCollectionHandler(
respTransformers())))));
putPipedHttpHandler(TYPE.COLLECTION, METHOD.PUT,
new RequestTransformerMetadataHandler(
new TransformerHandler(
new PutCollectionHandler(
respTransformers()),
PHASE.REQUEST,
new AggregationTransformer())));
putPipedHttpHandler(TYPE.COLLECTION, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteCollectionHandler(
respTransformers())));
putPipedHttpHandler(TYPE.COLLECTION, METHOD.PATCH,
new RequestTransformerMetadataHandler(
new TransformerHandler(
new PatchCollectionHandler(
respTransformers()),
PHASE.REQUEST,
new AggregationTransformer())));
// *** DOCUMENT handlers
putPipedHttpHandler(TYPE.DOCUMENT, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetDocumentHandler(
respTransformers())));
putPipedHttpHandler(TYPE.DOCUMENT, METHOD.PUT,
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new PutDocumentHandler(
new AfterWriteCheckMetadataHandler(
respTransformers())))));
putPipedHttpHandler(TYPE.DOCUMENT, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteDocumentHandler(
respTransformers())));
putPipedHttpHandler(TYPE.DOCUMENT, METHOD.PATCH,
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new PatchDocumentHandler(
new AfterWriteCheckMetadataHandler(
respTransformers())))));
// *** BULK_DOCUMENTS handlers, i.e. bulk operations
putPipedHttpHandler(TYPE.BULK_DOCUMENTS, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new BulkDeleteDocumentsHandler(
respTransformers())));
putPipedHttpHandler(TYPE.BULK_DOCUMENTS, METHOD.PATCH,
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new BulkPatchDocumentsHandler(
respTransformers()))));
// *** COLLECTION_INDEXES handlers
putPipedHttpHandler(TYPE.COLLECTION_INDEXES, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetIndexesHandler(
respTransformers())));
// *** INDEX handlers
putPipedHttpHandler(TYPE.INDEX, METHOD.PUT,
new RequestTransformerMetadataHandler(
new PutIndexHandler(
respTransformers())));
putPipedHttpHandler(TYPE.INDEX, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteIndexHandler(
respTransformers())));
// *** FILES_BUCKET and FILE handlers
putPipedHttpHandler(TYPE.FILES_BUCKET, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetCollectionHandler(
respTransformers())));
putPipedHttpHandler(TYPE.FILES_BUCKET, METHOD.POST,
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new PostBucketHandler(
respTransformers()))));
putPipedHttpHandler(TYPE.FILE, METHOD.PUT,
new RequestTransformerMetadataHandler(
new BeforeWriteCheckMetadataHandler(
new PutFileHandler(
respTransformers()))));
putPipedHttpHandler(TYPE.FILES_BUCKET, METHOD.PUT,
new RequestTransformerMetadataHandler(
new PutBucketHandler(
respTransformers())));
putPipedHttpHandler(TYPE.FILES_BUCKET, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteBucketHandler(
respTransformers())));
putPipedHttpHandler(TYPE.FILE, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetFileHandler(
respTransformers())));
putPipedHttpHandler(TYPE.FILE_BINARY, METHOD.GET,
new GetFileBinaryHandler(
new HookMetadataHandler()));
putPipedHttpHandler(TYPE.FILE, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteFileHandler(
respTransformers())));
// *** AGGREGATION handler
putPipedHttpHandler(TYPE.AGGREGATION, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetAggregationHandler(
respTransformers())));
// *** SCHEMA handlers
putPipedHttpHandler(TYPE.SCHEMA_STORE, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetCollectionHandler(
new TransformerHandler(
respTransformers(),
PHASE.RESPONSE,
new JsonSchemaTransformer()))));
putPipedHttpHandler(TYPE.SCHEMA_STORE, METHOD.PUT,
new RequestTransformerMetadataHandler(
new PutCollectionHandler(
respTransformers())));
putPipedHttpHandler(TYPE.SCHEMA_STORE, METHOD.POST,
new RequestTransformerMetadataHandler(
new CheckHandler(
new TransformerHandler(
new PostCollectionHandler(
respTransformers()),
PHASE.REQUEST,
new JsonSchemaTransformer()),
new JsonMetaSchemaChecker())));
putPipedHttpHandler(TYPE.SCHEMA_STORE, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteCollectionHandler(
respTransformers())));
putPipedHttpHandler(TYPE.SCHEMA, METHOD.GET,
new RequestTransformerMetadataHandler(
new GetDocumentHandler(
new TransformerHandler(
respTransformers(),
PHASE.RESPONSE,
new JsonSchemaTransformer()))));
putPipedHttpHandler(TYPE.SCHEMA, METHOD.PUT,
new RequestTransformerMetadataHandler(
new CheckHandler(
new TransformerHandler(
new PutDocumentHandler(
respTransformers()),
PHASE.REQUEST,
new JsonSchemaTransformer()),
new JsonMetaSchemaChecker())));
putPipedHttpHandler(TYPE.SCHEMA, METHOD.DELETE,
new RequestTransformerMetadataHandler(
new DeleteDocumentHandler(
respTransformers())));
}
private PipedHttpHandler respTransformers() {
return new ResponseTransformerMetadataHandler(
new TransformerHandler(
new HookMetadataHandler(
new ResponseSenderHandler()),
PHASE.RESPONSE,
new PlainJsonTransformer()));
}
/**
* Given a type and method, return the appropriate PipedHttpHandler which
* can handle this request
*
* @param type
* @param method
* @return the PipedHttpHandler
*/
public PipedHttpHandler getPipedHttpHandler(TYPE type, METHOD method) {
Map<METHOD, PipedHttpHandler> methodsMap = handlersMultimap.get(type);
return methodsMap != null ? methodsMap.get(method) : null;
}
/**
* Given a type and method, put in a PipedHttpHandler
*
* @param type the DB type
* @param method the HTTP method
* @param handler the PipedHttpHandler
*/
void putPipedHttpHandler(TYPE type, METHOD method, PipedHttpHandler handler) {
LOGGER.trace("putPipedHttpHandler( {}, {}, {} )", type, method, getHandlerToLog(handler).getClass().getCanonicalName());
Map<METHOD, PipedHttpHandler> methodsMap = handlersMultimap.get(type);
if (methodsMap == null) {
methodsMap = new HashMap<>();
handlersMultimap.put(type, methodsMap);
}
methodsMap.put(method, handler);
}
private PipedHttpHandler getHandlerToLog(PipedHttpHandler handler) {
if (handler instanceof BeforeWriteCheckMetadataHandler
|| handler instanceof RequestTransformerMetadataHandler
|| handler instanceof CheckHandler
|| handler instanceof TransformerHandler) {
return getHandlerToLog(handler.getNext());
} else {
return handler;
}
}
/**
* Code to execute before each handleRequest
*
* @param exchange the HttpServerExchange
* @param context the RequestContext
*/
protected void before(HttpServerExchange exchange, RequestContext context) {
}
/**
* code to execute after each handleRequest
*
* @param exchange the HttpServerExchange
* @param context the RequestContext
*/
protected void after(HttpServerExchange exchange, RequestContext context) {
}
/**
* Handle the request, delegating to the proper PipedHttpHandler
*
* @param exchange the HttpServerExchange
* @param context the RequestContext
* @throws Exception
*/
@Override
public final void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception {
if (context.getType() == TYPE.INVALID) {
LOGGER.debug(
"This is a bad request: returning a <{}> HTTP code",
HttpStatus.SC_BAD_REQUEST);
ResponseHelper.endExchangeWithMessage(
exchange,
context,
HttpStatus.SC_BAD_REQUEST,
"bad request");
responseSenderHandler.handleRequest(exchange, context);
return;
}
if (context.getMethod() == METHOD.OTHER) {
LOGGER.debug(
"This method is not allowed: returning a <{}> HTTP code",
HttpStatus.SC_METHOD_NOT_ALLOWED);
ResponseHelper.endExchangeWithMessage(
exchange,
context,
HttpStatus.SC_METHOD_NOT_ALLOWED,
"mentod " + context.getMethod().name() + " not allowed");
responseSenderHandler.handleRequest(exchange, context);
return;
}
if (context.isReservedResource()) {
LOGGER.debug(
"The resource is reserved: returning a <{}> HTTP code",
HttpStatus.SC_FORBIDDEN);
ResponseHelper.endExchangeWithMessage(
exchange,
context,
HttpStatus.SC_FORBIDDEN,
"reserved resource");
responseSenderHandler.handleRequest(exchange, context);
return;
}
final PipedHttpHandler httpHandler
= getPipedHttpHandler(context.getType(), context.getMethod());
if (httpHandler != null) {
before(exchange, context);
httpHandler.handleRequest(exchange, context);
after(exchange, context);
} else {
LOGGER.error(
"Can't find PipedHttpHandler({}, {})",
context.getType(), context.getMethod());
ResponseHelper.endExchangeWithMessage(
exchange,
context,
HttpStatus.SC_METHOD_NOT_ALLOWED,
"mentod " + context.getMethod().name() + " not allowed");
responseSenderHandler.handleRequest(exchange, context);
}
}
}