dpfmanager.shell.modules.server.post.HttpPostHandler.java Source code

Java tutorial

Introduction

Here is the source code for dpfmanager.shell.modules.server.post.HttpPostHandler.java

Source

/**
 * <h1>HttpPostHandler.java</h1> <p> This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version; or, at your
 * choice, under the terms of the Mozilla Public License, v. 2.0. SPDX GPL-3.0+ or MPL-2.0+. </p>
 * <p> 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 General Public License and the Mozilla Public License for more details. </p> <p> You should
 * have received a copy of the GNU General Public License and the Mozilla Public License along with
 * this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>
 * and at <a href="http://mozilla.org/MPL/2.0">http://mozilla.org/MPL/2.0</a> . </p> <p> NB: for the
 *  statement, include Easy Innova SL or other company/Person contributing the code. </p> <p> 
 * 2015 Easy Innova, SL </p>
 *
 * @author Adri Llorens
 * @version 1.0
 * @since 23/7/2015
 */

package dpfmanager.shell.modules.server.post;

import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import dpfmanager.shell.core.DPFManagerProperties;
import dpfmanager.shell.core.config.BasicConfig;
import dpfmanager.shell.core.context.DpfContext;
import dpfmanager.shell.modules.messages.messages.ExceptionMessage;
import dpfmanager.shell.modules.messages.messages.LogMessage;
import dpfmanager.shell.modules.server.messages.PostMessage;
import dpfmanager.shell.modules.server.messages.StatusMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpData;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.util.CharsetUtil;

import com.google.gson.Gson;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.net.util.Base64;
import org.apache.logging.log4j.Level;
import org.apache.tools.zip.ZipEntry;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.zip.ZipOutputStream;

public class HttpPostHandler extends SimpleChannelInboundHandler<HttpObject> {

    private final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);

    private DpfContext context;
    private ResourceBundle bundle;

    private HttpRequest request;
    private HttpData partialContent;
    private final StringBuilder responseContent = new StringBuilder();
    private HttpPostRequestDecoder decoder;

    // Request params
    private Long uuid;
    private Long id;
    private String filepath;
    private String configpath;
    private File destFolder;

    static {
        // should delete file on exit (in normal exit)
        DiskAttribute.deleteOnExitTemporaryFile = true;
        DiskFileUpload.deleteOnExitTemporaryFile = true;
        // system temp directory
        DiskFileUpload.baseDirectory = null;
        DiskAttribute.baseDirectory = null;
    }

    public HttpPostHandler(DpfContext context) {
        this.context = context;
        bundle = DPFManagerProperties.getBundle();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            decoder.cleanFiles();
        }
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {
            HttpRequest request = this.request = (HttpRequest) msg;
            if (request.method().equals(HttpMethod.POST) || request.method().equals(HttpMethod.OPTIONS)) {
                // Start new POST request
                init();
                decoder = new HttpPostRequestDecoder(factory, request);
            } else {
                sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
                return;
            }
        }

        // POST request
        if (decoder != null) {
            if (msg instanceof HttpContent) {
                // New chunk is received
                try {
                    HttpContent chunk = (HttpContent) msg;
                    decoder.offer(chunk);
                    readHttpDataChunkByChunk();
                    if (chunk instanceof LastHttpContent) {
                        tractReadPost(ctx);
                        reset();
                        return;
                    }
                } catch (ErrorDataDecoderException e1) {
                    e1.printStackTrace();
                    ctx.channel().close();
                    return;
                }
            }
        }
    }

    private void init() {
        // Init new request
        uuid = System.currentTimeMillis();
        id = null;
        filepath = null;
        configpath = null;
    }

    private void reset() {
        // End of request, destroy decoder
        decoder.destroy();
        decoder = null;
        request = null;
    }

    private void tractReadPost(ChannelHandlerContext ctx) {
        if (id != null) {
            askForJob(ctx);
        } else {
            newFileCheck(ctx);
        }
    }

    /**
     * Main methods
     */
    private void askForJob(ChannelHandlerContext ctx) {
        StatusMessage sm = (StatusMessage) context.sendAndWaitResponse(BasicConfig.MODULE_DATABASE,
                new PostMessage(PostMessage.Type.ASK, id));
        Map<String, String> map = new HashMap<>();
        map.put("type", "ASK");
        map.put("id", id.toString());
        if (sm.isRunning()) {
            map.put("status", "Running");
            map.put("processed", sm.getProcessed().toString());
            map.put("total", sm.getTotal().toString());
        } else if (sm.isFinished()) {
            // Zip folder
            String path = zipFolder(sm.getFolder());
            String link = parsePathToLink(path);
            map.put("status", "Finished");
            map.put("path", link);
            printOut("Finished check, sending to user...");
            printOut("  Uuid: " + id.toString());
            printOut("  Path: " + path);
        } else {
            map.put("status", "NotFound");
        }
        responseContent.append(new Gson().toJson(map));
        writeResponse(ctx.channel());
    }

    private void newFileCheck(ChannelHandlerContext ctx) {
        Map<String, String> map = new HashMap<>();
        map.put("type", "CHECK");
        map.put("id", uuid.toString());
        map.put("input", getName(filepath));
        if (configpath == null) {
            // Error miss config file
            map.put("myerror", bundle.getString("missingConfig"));
        } else if (filepath == null) {
            // Error miss file to check
            map.put("myerror", bundle.getString("missingFile"));
        }
        responseContent.append(new Gson().toJson(map));
        writeResponse(ctx.channel());

        // OK start the check
        if (!map.containsKey("myerror")) {
            context.send(BasicConfig.MODULE_SERVER,
                    new PostMessage(PostMessage.Type.POST, uuid, filepath, configpath));
            printOut("");
            printOut("New file check received.");
            printOut("  Uuid: " + uuid);
        } else {
            deleteTmpFolder(uuid);
        }
    }

    private void deleteTmpFolder(Long uuid) {
        try {
            File folder = new File(DPFManagerProperties.getServerDir() + "/" + uuid);
            if (folder.exists() && folder.isDirectory()) {
                FileUtils.deleteDirectory(folder);
            }
        } catch (Exception e) {
            context.send(BasicConfig.MODULE_MESSAGE, new ExceptionMessage("Exception in remove server folder", e));
        }
    }

    private String getName(String path) {
        String ret = "";
        if (path != null) {
            if (path.contains(";")) {
                // List files
                String[] paths = path.split(";");
                for (String newPath : paths) {
                    ret = ret + newPath.substring(newPath.lastIndexOf("\\") + 1, newPath.length()) + ", ";
                }
                ret = ret.substring(0, ret.length() - 1);
            } else {
                ret = path.substring(path.lastIndexOf("\\") + 1, path.length());
            }
        }
        return ret;
    }

    /**
     * Zip functoins
     */
    public String parsePathToLink(String path) {
        String[] splited = path.split("/");
        String last = splited[splited.length - 1];
        String last2 = splited[splited.length - 2];
        return "/" + last2 + "/" + last;
    }

    public String zipFolder(String folder) {
        // Zip path
        String outputFile = folder + ".zip";
        if (folder.endsWith("/")) {
            outputFile = folder.substring(0, folder.length() - 1) + ".zip";
        }
        // Check if exists
        if (new File(outputFile).exists()) {
            return outputFile;
        }
        // Make the zip
        try {
            ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(outputFile));
            compressDirectoryToZipfile(folder, folder, zipFile);
            IOUtils.closeQuietly(zipFile);
            return outputFile;
        } catch (Exception e) {
            return null;
        }
    }

    private void compressDirectoryToZipfile(String rootDir, String sourceDir, ZipOutputStream out)
            throws IOException, FileNotFoundException {
        for (File file : new File(sourceDir).listFiles()) {
            if (file.isDirectory()) {
                compressDirectoryToZipfile(rootDir, sourceDir + file.getName() + "/", out);
            } else {
                ZipEntry entry = new ZipEntry(sourceDir.replace(rootDir, "") + file.getName());
                out.putNextEntry(entry);

                FileInputStream in = new FileInputStream(sourceDir + file.getName());
                IOUtils.copy(in, out);
                IOUtils.closeQuietly(in);
            }
        }
    }

    /**
     * Example of reading request by chunk and getting values from chunk to chunk
     */
    private void readHttpDataChunkByChunk() {
        try {
            while (decoder.hasNext()) {
                InterfaceHttpData data = decoder.next();
                if (data != null) {
                    if (partialContent == data) {
                        partialContent = null;
                    }
                    try {
                        if (data.getHttpDataType() == HttpDataType.Attribute) {
                            parseAttributeData((Attribute) data);
                        } else if (data.getHttpDataType() == HttpDataType.FileUpload) {
                            parseFileUploadData((FileUpload) data);
                        } else {
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        return;
                    } finally {
                        data.release();
                    }
                }
            }
        } catch (HttpPostRequestDecoder.EndOfDataDecoderException e1) {
            // End
        }
    }

    private void parseAttributeData(Attribute attribute) throws IOException {
        String name = attribute.getName();
        if (name.equals("id")) {
            id = Long.parseLong(attribute.getValue());
        } else if (name.equals("config")) {
            destFolder = createNewDirectory(uuid);
            String encoded = attribute.getValue();
            String decoded = new String(Base64.decodeBase64(encoded), "UTF-8");
            File dest = new File(destFolder.getAbsolutePath() + "/config.dpf");
            configpath = dest.getAbsolutePath();
            FileUtils.writeStringToFile(dest, decoded);
        }
    }

    private void parseFileUploadData(FileUpload fileUpload) throws IOException {
        if (fileUpload.isCompleted()) {
            destFolder = createNewDirectory(uuid);
            if (fileUpload.getName().equals("config")) {
                // Save config file
                File dest = new File(destFolder.getAbsolutePath() + "/config.dpf");
                configpath = dest.getAbsolutePath();
                fileUpload.renameTo(dest);
            } else {
                // Save file to check
                File dest = new File(destFolder.getAbsolutePath() + "/" + fileUpload.getFilename());
                if (filepath == null) {
                    filepath = dest.getAbsolutePath();
                } else {
                    filepath = filepath + ";" + dest.getAbsolutePath();
                }
                fileUpload.renameTo(dest);
            }
        }
    }

    private File createNewDirectory(Long uuid) {
        String serverDir = DPFManagerProperties.getServerDir();
        File folder = new File(serverDir + "/" + uuid);
        if (folder.exists()) {
            return folder;
        }
        folder.mkdirs();
        return folder;
    }

    /**
     * Util functions
     */

    private void writeResponse(Channel channel) {
        // Convert the response content to a ChannelBuffer.
        ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
        responseContent.setLength(0);

        // Decide whether to close the connection or not.
        boolean close = request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)
                || request.protocolVersion().equals(HttpVersion.HTTP_1_0) && !request.headers()
                        .contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true);

        // Build the response object.
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

        if (!close) {
            response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
        }

        // Extra headers
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");

        // Write the response.
        ChannelFuture future = channel.writeAndFlush(response);
        // Close the connection after the write operation is done if necessary.
        if (close) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status,
                Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private void printOut(String message) {
        context.send(BasicConfig.MODULE_MESSAGE, new LogMessage(getClass(), Level.DEBUG, message));
    }

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