io.blobkeeper.server.handler.FileWriterHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.blobkeeper.server.handler.FileWriterHandler.java

Source

package io.blobkeeper.server.handler;

/*
 * Copyright (C) 2015 by Denis M. Gabaydulin
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

import io.blobkeeper.cluster.service.ClusterMembershipService;
import io.blobkeeper.common.domain.Result;
import io.blobkeeper.common.domain.api.ApiRequest;
import io.blobkeeper.common.domain.api.ReturnValue;
import io.blobkeeper.common.service.IdGeneratorService;
import io.blobkeeper.file.configuration.FileConfiguration;
import io.blobkeeper.file.domain.StorageFile;
import io.blobkeeper.index.domain.IndexTempElt;
import io.blobkeeper.index.service.IndexService;
import io.blobkeeper.server.handler.api.RequestHandler;
import io.blobkeeper.server.handler.api.RequestMapper;
import io.blobkeeper.file.service.WriterTaskQueue;
import io.blobkeeper.server.util.HttpUtils;
import io.blobkeeper.server.util.MetadataParser;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import org.slf4j.Logger;

import javax.inject.Inject;
import java.io.IOException;

import static io.blobkeeper.common.domain.Error.createError;
import static io.blobkeeper.common.domain.ErrorCode.*;
import static io.blobkeeper.index.domain.IndexElt.DEFAULT_TYPE;
import static io.blobkeeper.server.util.HttpUtils.*;
import static io.netty.handler.codec.http.HttpMethod.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.LastHttpContent.EMPTY_LAST_CONTENT;
import static io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType.FileUpload;
import static java.lang.String.format;
import static org.joda.time.DateTime.now;
import static org.joda.time.DateTimeZone.UTC;
import static org.slf4j.LoggerFactory.getLogger;

public class FileWriterHandler extends BaseFileHandler<HttpObject> {
    private static final Logger log = getLogger(FileWriterHandler.class);

    private static final int DEFAULT_SHARD_ID = 1;

    @Inject
    private IdGeneratorService isGeneratorService;

    @Inject
    private IndexService indexService;

    @Inject
    private WriterTaskQueue writerTaskQueue;

    @Inject
    private ClusterMembershipService clusterMembershipService;

    @Inject
    private RequestMapper requestMapper;

    @Inject
    private FileConfiguration fileConfiguration;

    private HttpRequest request;

    private HttpCustomPostRequestDecoder decoder;

    private boolean requestIsSent = false;
    private boolean errorRequest = false;

    // clean up garbage
    static {
        DiskFileUpload.deleteOnExitTemporaryFile = false;
        DiskAttribute.deleteOnExitTemporaryFile = true;
    }

    // set up base upload directory
    public void init() {
        DiskFileUpload.baseDirectory = fileConfiguration.getUploadPath();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.warn("Unknown exception", cause);
        ctx.channel().close();
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            reset();
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            errorRequest = true;
            reset();
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext context, HttpObject object) throws Exception {
        setContext();

        log.debug("{}", object);

        if (object instanceof HttpRequest) {
            HttpRequest request = (HttpRequest) object;

            if (request.getMethod() == GET) {
                jumpToReader(context, object);
                return;
            }

            if (request.getMethod() == DELETE) {
                jumpToDeleter(context, object);
                return;
            }

            if (request.getMethod() == PUT) {
                // FIXME: restore will be here
                return;
            }
        }

        if (object.equals(EMPTY_LAST_CONTENT)) {
            String errorMessage = "There is no upload file in the request";
            log.error(errorMessage);
            sendError(context, BAD_REQUEST, createError(INVALID_REQUEST, errorMessage));
            return;
        }

        createPostDecoderIfNotExists(context, object);

        if (null == decoder) {
            // FIXME: send error
            return;
        }

        if (object instanceof HttpContent) {
            // new chunk is received
            HttpContent chunk = (HttpContent) object;
            try {
                decoder.offer(chunk);
            } catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
                log.error("Can't decode chunk", e1);

                sendError(context, BAD_REQUEST, createError(INVALID_REQUEST, "Can't decode chunk"));
                return;
            }

            readHttpDataChunkByChunk(context);

            if (chunk instanceof LastHttpContent) {
                if (!requestIsSent) {
                    String errorMessage = "There is no upload file in the request";
                    log.error(errorMessage);
                    sendError(context, BAD_REQUEST, createError(INVALID_REQUEST, errorMessage));
                }
                reset();
            }
        }
    }

    @Override
    protected void sendError(ChannelHandlerContext ctx, HttpResponseStatus status,
            io.blobkeeper.common.domain.Error error) {
        this.errorRequest = true;
        super.sendError(ctx, status, error);
    }

    private void handleApiRequest(ChannelHandlerContext ctx, String value) {
        try {
            RequestHandler<?, ? extends ApiRequest> requestHandler = requestMapper.getByUri(this.request.getUri());
            ReturnValue<?> returnValue = requestHandler.handleRequest(value);
            writeResponse(ctx, returnValue, request);
        } catch (Exception e) {
            log.error("Can't handle request", e);
        }
    }

    private void jumpToDeleter(ChannelHandlerContext context, HttpObject object) {
        context.pipeline().addBefore("deleter", "aggregator", new HttpObjectAggregator(65536));
        context.pipeline().remove(FileWriterHandler.class);
        context.fireChannelRead(object);
    }

    private void jumpToReader(ChannelHandlerContext context, HttpObject object) {
        context.pipeline().addBefore("reader", "aggregator", new HttpObjectAggregator(65536));
        context.pipeline().remove(this);
        context.fireChannelRead(object);
    }

    private void createPostDecoderIfNotExists(ChannelHandlerContext ctx, HttpObject request) {
        if (decoder == null) {
            try {
                this.request = (HttpRequest) request;
                // always save data on disk
                CustomHttpDataFactory dataFactory = new CustomHttpDataFactory();
                decoder = new HttpCustomPostRequestDecoder(dataFactory, this.request);
            } catch (Exception e) {
                log.error("Can't decode message", e);
                sendError(ctx, BAD_REQUEST, createError(INVALID_REQUEST, "Can't decode request"));
            }
        }
    }

    private void reset() {
        log.debug("Release all resources from decoder started");

        if (errorRequest) {
            cleanFiles();
        }

        request = null;
        decoder.destroy();
        decoder = null;

        log.debug("Release all resources from decoder completed");
    }

    private void cleanFiles() {
        decoder.cleanFilesOnError();
    }

    private void readHttpDataChunkByChunk(ChannelHandlerContext ctx) {
        try {
            while (decoder.hasNext()) {
                InterfaceHttpData data = decoder.next();
                if (data != null) {
                    // new value
                    writeHttpData(ctx, data);
                }
            }
        } catch (HttpPostRequestDecoder.EndOfDataDecoderException e1) {
            // FIXME: send error?
            log.debug("End of a request!");
        }
    }

    private void writeHttpData(ChannelHandlerContext ctx, InterfaceHttpData data) {
        if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
            Attribute attribute = (Attribute) data;
            String value;
            try {
                value = attribute.getValue();
                log.info("Api call: {}", value);

                handleApiRequest(ctx, value);
                requestIsSent = true;
                return;
            } catch (IOException e) {
                log.error(format("Can't read data, attribute name is %s", attribute.getHttpDataType().name()), e);

                sendError(ctx, BAD_REQUEST, createError(INVALID_REQUEST, "Can't read data"));
                return;
            }
        } else {
            if (data.getHttpDataType() == FileUpload) {
                requestIsSent = true;

                if (!clusterMembershipService.isMaster()) {
                    log.error("Node is not a master");
                    sendError(ctx, METHOD_NOT_ALLOWED, createError(NOT_A_MASTER, "Node is not a master"));
                    return;
                }

                String uri = request.getUri();
                long id = getId(uri);
                int type = getType(uri);

                FileUpload fileUpload = (FileUpload) data;
                if (fileUpload.isCompleted()) {
                    if (id == HttpUtils.NOT_FOUND || type == HttpUtils.NOT_FOUND) {
                        id = isGeneratorService.generate(DEFAULT_SHARD_ID);
                        type = DEFAULT_TYPE;
                        log.info("New id : type is {} : {}", id, type);
                    } else {
                        log.info("Given file : type is {} : {}", id, type);

                        if (null != indexService.getById(id, type)) {
                            log.error("File {} : {} is already present");
                            sendError(ctx, CONFLICT, createError(ALREADY_EXISTS, "Object already exists"));
                            return;
                        }
                    }

                    log.trace("Started copying a file to the write queue");

                    StorageFile storageFile = buildStorageFile(fileUpload).id(id).type(type)
                            .headers(MetadataParser.getHeaders(request)).build();

                    addTempIndex(storageFile);

                    // add file to the upload queue
                    if (!writerTaskQueue.offer(storageFile)) {
                        String errorMessage = "Upload failed";
                        log.error(errorMessage);
                        sendError(ctx, BAD_GATEWAY, createError(SERVICE_ERROR, errorMessage));
                    } else {
                        log.info("File {} added to the upload queue", id);
                        writeResponse(ctx, new ReturnValue<>(new Result(id)), request);
                        return;
                    }
                } else {
                    String errorMessage = "Upload file to be continued but should not!";
                    log.error(errorMessage);
                    sendError(ctx, BAD_REQUEST, createError(INVALID_REQUEST, errorMessage));
                }
            }
        }

        if (!requestIsSent) {
            String errorMessage = "There is no upload file in the request";
            log.error(errorMessage);
            sendError(ctx, BAD_REQUEST, createError(INVALID_REQUEST, errorMessage));
        }
    }

    /**
     * Save a temp index for recovering
     */
    private void addTempIndex(StorageFile storageFile) {
        IndexTempElt indexElt = new IndexTempElt.IndexTempEltBuilder().id(storageFile.getId())
                .type(storageFile.getType()).created(now(UTC).getMillis()).metadata(storageFile.getMetadata())
                .file(storageFile.getFile().getAbsolutePath()).build();

        indexService.add(indexElt);
    }
}