io.urmia.job.handler.JobHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.urmia.job.handler.JobHandler.java

Source

package io.urmia.job.handler;

/**
 *
 * Copyright 2014 by Amin Abbaspour
 *
 * This file is part of Urmia.io
 *
 * Urmia.io 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.
 *
 * Urmia.io 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Urmia.io.  If not, see <http://www.gnu.org/licenses/>.
 */

import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AttributeKey;
import io.urmia.md.model.storage.FullObjectName;
import io.urmia.proxy.HttpProxyFrontendHandler;
import io.urmia.job.pub.*;
import io.urmia.md.model.job.*;
import io.urmia.md.service.MetadataService;
import io.urmia.naming.model.NodeType;
import io.urmia.naming.service.NamingService;
import io.urmia.util.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.x.discovery.ServiceInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static io.netty.handler.codec.http.HttpResponseStatus.ACCEPTED;
import static io.netty.handler.codec.http.HttpResponseStatus.CREATED;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

public class JobHandler extends SimpleChannelInboundHandler<JobRequest> {

    private static final Logger log = LoggerFactory.getLogger(JobHandler.class);

    // todo: weak reference or TTL cache
    private static final Map<String, JobQueue> jobQueueMap = new LinkedHashMap<String, JobQueue>();
    private static final Map<String, JobStatusUpdateHandler> statUpdaterMap = new LinkedHashMap<String, JobStatusUpdateHandler>();

    private final CuratorFramework client;
    private final MetadataService mds;
    private final JobStatusMonitor statusMon;
    private final NamingService ns;

    public JobHandler(CuratorFramework client, MetadataService mds, NamingService ns) {
        this.client = client;
        this.mds = mds;
        this.statusMon = new ZkJobStatusMonitor(client);
        this.ns = ns;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        JobRequest jobRequest = ctx.attr(jobAttributeKey).getAndRemove();
        log.info("channelInactive jobRequest: {} in ctx: {}", jobRequest, ctx);

        //if(jobRequest == null)
        //    return;
        //log.info("publishing EOT as channel inactive for job: {}", jobRequest);
        //input(new JobInputRequest(jobRequest, JobInput.end()));
    }

    public static final AttributeKey<JobRequest> jobAttributeKey = AttributeKey.valueOf("jobRequest");

    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, final JobRequest msg) throws Exception {

        log.info("channelRead0 received: {}", msg);

        String location = msg.objectName.owner + '/' + msg.objectName.ns.path + '/' + msg.getId();

        if (StringUtils.isBlank(location)) {
            final HttpResponse httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                    HttpResponseStatus.BAD_REQUEST);
            ctx.writeAndFlush(httpResp);
            ctx.channel().close();
            return;
        }

        if (msg instanceof JobQueryRequest) {
            JobQueryRequest queryRequest = (JobQueryRequest) msg;
            Optional<String> status = query(queryRequest);
            final HttpResponse httpResp;

            if (status.isPresent()) {
                byte[] statusByes = status.get().getBytes();
                httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK,
                        Unpooled.wrappedBuffer(statusByes));
                httpResp.headers().set("Content-Type", "application/json");
                httpResp.headers().set("Content-Length", statusByes.length);
                httpResp.headers().set("Connection", "close");
            } else {
                String err = String.format("{\"code\":\"ResourceNotFound\",\"message\":\"%s was not found\"}",
                        location);
                httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND,
                        Unpooled.wrappedBuffer(err.getBytes()));
                httpResp.headers().set("Content-Length", err.length());
                httpResp.headers().set("Connection", "close");
            }
            ctx.writeAndFlush(httpResp);
            return;
        }

        if (msg instanceof JobCancelRequest) {
            JobCancelRequest cancelRequest = (JobCancelRequest) msg;

            cancel(cancelRequest);
            final HttpResponse httpResp;
            httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, ACCEPTED);
            httpResp.headers().set("Content-Type", "application/json");
            httpResp.headers().set("Content-Length", 0);
            httpResp.headers().set("Connection", "close");

            ctx.writeAndFlush(httpResp);
            return;
        }

        if (msg instanceof JobCreateRequest) {
            JobCreateRequest createRequest = (JobCreateRequest) msg;
            register(createRequest);

            ctx.attr(jobAttributeKey).setIfAbsent(msg);
            log.info("added jobReq {} to ctx: {}", msg, ctx);

            DefaultFullHttpResponse httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, CREATED);
            httpResp.headers().add("location", msg.getId());
            httpResp.headers().add("Content-Length", 0);

            ctx.writeAndFlush(httpResp);
            return;
        }

        if (msg instanceof JobInputRequest) {
            JobInputRequest inputRequest = (JobInputRequest) msg;
            input(inputRequest);

            boolean end = inputRequest.input.isEod();
            final HttpResponse httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, CREATED);
            httpResp.headers().add("Content-Length", 0);
            ctx.writeAndFlush(httpResp);

            if (end)
                ctx.channel().close();
            return;

        }

        if (msg instanceof JobGetRequest) {
            JobGetRequest getRequest = (JobGetRequest) msg;
            setupProxyGet(ctx, getRequest);
            return;
        }

        log.error("invalid input");
        final HttpResponse httpResp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.BAD_REQUEST);
        ctx.writeAndFlush(httpResp);
        ctx.channel().close();

    }

    private void register(JobCreateRequest msg) throws Exception {

        JobStatusUpdateHandler handler = statusMon.register(msg.getId(), msg.job);
        statUpdaterMap.put(msg.getId(), handler);

        JobQueue q = new InputAwareZkJobQueue(client, mds, ns, msg, handler);
        jobQueueMap.put(msg.getId(), q);

        log.info("registered queue against job: {}", msg.getId());

    }

    private void input(JobInputRequest msg) throws Exception {

        log.debug("publish msg of: {}", msg);

        JobQueue q = jobQueueMap.get(msg.getId());

        if (q == null) {
            log.warn("discarding input. no job queue found for id: {}", msg.getId());
            return;
        }

        if (msg.input.isEod()) {
            log.warn("EOT for job: {}", msg.getId());
            jobQueueMap.remove(msg.getId());
            q.close();
        } else {
            log.info("publishing to q: {}, input: {}", q, msg.input);
            addToInputs(msg);
            q.put(msg.input);
        }
    }

    private void addToInputs(JobInputRequest input) throws Exception {
        String insPath = "/urmia/1/jobs/" + input.objectName.owner + "/" + input.getId() + "/live/in.txt";

        byte[] existing = new byte[0];
        try {
            existing = client.getData().forPath(insPath);
        } catch (Exception e) {
            log.warn("unable to fetch from {}, msg: {}", insPath, e.getMessage());
        }

        client.create().creatingParentsIfNeeded().inBackground().forPath(insPath,
                StringUtils.append(existing, input.input.toBytes()));
    }

    private boolean cancel(JobCancelRequest q) {
        log.info("please implement cancel: {}", q);
        return true;
    }

    private Optional<String> query(JobQueryRequest q) {
        JobQueryRequest.Type type = q.type;

        if (type == JobQueryRequest.Type.Status)
            try {
                Optional<JobStatus> status = statusMon.getStatus(q.getId(), handler(q.getId()));
                return Optional.of(status.get().toString());
            } catch (Exception e) {
                log.error("exception in query {}, msg: {}", q, e.getMessage());
                return Optional.absent();
            }

        if (type == JobQueryRequest.Type.Outputs) {
            String outPath = "/urmia/1/jobs/" + q.objectName.owner + "/" + q.getId() + "/live/out.txt";

            log.debug("scanning path for query output: {}", outPath);

            try {
                String outputs = new String(client.getData().forPath(outPath)).trim();
                return Optional.of(outputs);
            } catch (Exception e) {
                log.error("exception in query {}, msg: {}", q, e.getMessage());
                return Optional.absent();
            }
        }

        if (type == JobQueryRequest.Type.Inputs) {
            String inPath = "/urmia/1/jobs/" + q.objectName.owner + "/" + q.getId() + "/live/in.txt";

            log.debug("scanning path for query inputs: {}", inPath);

            try {
                String inputs = new String(client.getData().forPath(inPath)).trim();
                return Optional.of(inputs);
            } catch (Exception e) {
                log.error("exception in query {}, msg: {}", q, e.getMessage());
                return Optional.absent();
            }
        }

        if (type == JobQueryRequest.Type.List) {
            String listPath = "/urmia/1/jobs/" + q.objectName.owner;

            log.debug("scanning path for query list: {}", listPath);

            StringBuilder sb = new StringBuilder();
            try {
                List<String> children = client.getChildren().forPath(listPath);
                for (String child : children)
                    sb.append(String.format(
                            "{\"name\":\"%s\",\"type\":\"directory\",\"mtime\":\"2014-07-24T04:50:30.573Z\"}\n",
                            child));

                return Optional.of(sb.toString());
            } catch (Exception e) {
                log.error("exception in query {}, msg: {}", q, e.getMessage());
                return Optional.absent();
            }
        }

        return Optional.absent();
    }

    private JobStatusUpdateHandler handler(String jobId) {
        JobStatusUpdateHandler handler = statUpdaterMap.get(jobId);
        if (handler != null)
            return handler;

        handler = new ZkJobStatusUpdaterHandler(client, jobId);
        statUpdaterMap.put(jobId, handler);
        return handler;
    }

    private void setupProxyGet(final ChannelHandlerContext ctx, JobGetRequest getRequest) throws Exception {
        ServiceInstance<NodeType> ods = ns.get(NodeType.ODS, getRequest.getStorageNodeId());

        List<ServiceInstance<NodeType>> l = Lists.newArrayList(ods);
        log.info("proxy storage node: {}", ods);
        HttpRequest initHttpRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
                getRequest.objectName.toString());
        HttpProxyFrontendHandler proxy = new HttpProxyFrontendHandler(l, mds, initHttpRequest, ctx, true,
                Optional.<FullObjectName>absent());

        ctx.pipeline().remove("encoder");
        ctx.pipeline().remove("aggregator");
        ctx.pipeline().remove("job-decoder");
        ctx.pipeline().remove("job");

        ctx.pipeline().addLast("proxy", proxy);

    }
}