io.qdb.server.controller.InputController.java Source code

Java tutorial

Introduction

Here is the source code for io.qdb.server.controller.InputController.java

Source

/*
 * Copyright 2013 David Tinker
 *
 * Licensed 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.
 */

package io.qdb.server.controller;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import io.qdb.server.databind.DataBinder;
import io.qdb.server.databind.DurationParser;
import io.qdb.server.databind.HasAnySetter;
import io.qdb.server.input.InputHandler;
import io.qdb.server.input.InputHandlerFactory;
import io.qdb.server.input.InputStatusMonitor;
import io.qdb.server.model.Input;
import io.qdb.server.model.Queue;
import io.qdb.server.monitor.Status;
import io.qdb.server.repo.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

@Singleton
public class InputController extends CrudController {

    private final Repository repo;
    private final InputHandlerFactory handlerFactory;
    private final InputStatusMonitor inputStatusMonitor;

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

    public static class InputDTO implements Comparable<InputDTO>, HasAnySetter {

        public String id;
        public Integer version;
        public String type;
        public String url;
        public Boolean enabled;
        public Integer updateIntervalMs;
        public Object warnAfter;
        public Object errorAfter;
        public transient Map<String, Object> params;

        public String status;
        public Long lastMessageId;
        public Date lastMessageTimestamp;
        public Object lastMessageAppended;

        @SuppressWarnings("UnusedDeclaration")
        public InputDTO() {
        }

        public InputDTO(String id, Input in, boolean borg) {
            this.id = id;
            version = in.getVersion();
            type = in.getType();
            url = in.getUrl();
            enabled = in.isEnabled();
            updateIntervalMs = in.getUpdateIntervalMs();
            params = in.getParams();
            lastMessageId = null0(in.getLastMessageId());
            lastMessageTimestamp = toDate(in.getLastMessageTimestamp());

            if (borg) {
                this.warnAfter = null0(in.getWarnAfter());
                this.errorAfter = null0(in.getErrorAfter());
            } else {
                int secs = in.getWarnAfter();
                if (secs > 0)
                    this.warnAfter = DurationParser.formatHumanMs(secs * 1000L);
                secs = in.getErrorAfter();
                if (secs > 0)
                    this.errorAfter = DurationParser.formatHumanMs(secs * 1000L);
            }
        }

        private Date toDate(long ms) {
            return ms == 0 ? null : new Date(ms);
        }

        private Integer null0(int x) {
            return x == 0 ? null : x;
        }

        private Long null0(long x) {
            return x == 0 ? null : x;
        }

        @JsonAnySetter
        public void set(String key, Object value) {
            if (params == null)
                params = new HashMap<String, Object>();
            if (value == null)
                params.remove(key);
            else
                params.put(key, value);
        }

        @JsonAnyGetter
        public Map<String, Object> getParams() {
            return params;
        }

        @Override
        public int compareTo(InputDTO in) {
            return id.compareTo(in.id);
        }
    }

    private static final Pattern VALID_INPUT_ID = Pattern.compile("[0-9a-z\\-_]+", Pattern.CASE_INSENSITIVE);

    @Inject
    public InputController(JsonService jsonService, Repository repo, InputHandlerFactory handlerFactory,
            InputStatusMonitor inputStatusMonitor) {
        super(jsonService);
        this.repo = repo;
        this.handlerFactory = handlerFactory;
        this.inputStatusMonitor = inputStatusMonitor;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void list(Call call, int offset, int limit) throws IOException {
        List<InputDTO> ans = new ArrayList<InputDTO>();
        Queue q = call.getQueue();
        Map<String, String> inputs = q.getInputs();
        if (inputs != null) {
            for (Map.Entry<String, String> e : inputs.entrySet()) {
                Input in = repo.findInput(e.getValue());
                if (in != null)
                    ans.add(createInputDTO(call, e.getKey(), in));
            }
            Collections.sort(ans);
            int last = Math.min(offset + limit, ans.size());
            if (offset > 0 || last < ans.size()) {
                if (offset >= ans.size())
                    ans = Collections.EMPTY_LIST;
                else
                    ans = ans.subList(offset, last);
            }
        }
        call.setJson(ans);
    }

    @Override
    protected void count(Call call) throws IOException {
        Map<String, String> inputs = call.getQueue().getInputs();
        call.setJson(new Count(inputs == null ? 0 : inputs.size()));
    }

    @Override
    protected void show(Call call, String id) throws IOException {
        Queue q = call.getQueue();
        Map<String, String> inputs = q.getInputs();
        if (inputs != null) {
            String inputId = inputs.get(id);
            if (inputId != null) {
                Input in = repo.findInput(inputId);
                if (in != null) {
                    call.setJson(createInputDTO(call, id, in));
                    return;
                }
            }
        }
        call.setCode(404);
    }

    private InputDTO createInputDTO(Call call, String id, Input in) throws IOException {
        boolean borg = call.getBoolean("borg");
        InputDTO dto = new InputDTO(id, in, borg);
        if (dto.lastMessageTimestamp != null) {
            long ms = System.currentTimeMillis() - dto.lastMessageTimestamp.getTime();
            dto.lastMessageAppended = borg ? ms : DurationParser.formatHumanMs(ms) + " ago";
        }
        Status status = inputStatusMonitor.getStatus(in);
        if (status != null)
            dto.status = status.toString();
        return dto;
    }

    @Override
    protected void createOrUpdate(Call call, String id) throws IOException {
        InputDTO dto = getBodyObject(call, InputDTO.class);
        boolean create;
        Input in;
        Queue q;
        synchronized (repo) {
            // re-lookup queue inside sync block in case we need to update it
            q = repo.findQueue(call.getQueue().getId());
            if (q == null) { // this isn't likely but isn't impossible either
                call.setCode(404);
                return;
            }

            String inputId = q.getInputIdForInput(id);
            if (create = inputId == null) {
                if (call.isPut()) {
                    call.setCode(404);
                    return;
                }
                if (!VALID_INPUT_ID.matcher(id).matches()) {
                    call.setCode(400, "Input id must contain only letters, numbers, hyphens and underscores");
                    return;
                }
                if (dto.type == null) {
                    call.setCode(400, "type is required");
                    return;
                }
                in = new Input();
                in.setQueue(q.getId());
                in.setEnabled(true);
                in.setUpdateIntervalMs(1000);
            } else {
                in = repo.findInput(inputId);
                if (in == null) { // this shouldn't happen
                    String msg = "Input /db/" + q.getDatabase() + "/q/" + q.getId() + "/in/" + id + " inputId ["
                            + inputId + "] not found";
                    log.error(msg);
                    call.setCode(500, msg);
                    return;
                }
                if (dto.version != null && !dto.version.equals(in.getVersion())) {
                    call.setCode(409, createInputDTO(call, id, in));
                    return;
                }
                in = in.deepCopy();
            }

            boolean changed = create;

            if (dto.type != null && !dto.type.equals(in.getType())) {
                try {
                    handlerFactory.createHandler(dto.type);
                } catch (IllegalArgumentException e) {
                    call.setCode(400, e.getMessage());
                    return;
                }
                in.setType(dto.type);
                changed = true;
            }

            if (dto.url != null && !dto.url.equals(in.getUrl())) {
                in.setUrl(dto.url);
                changed = true;
            }

            if (dto.enabled != null && dto.enabled != in.isEnabled()) {
                in.setEnabled(dto.enabled);
                changed = true;
            }

            if (dto.updateIntervalMs != null && dto.updateIntervalMs != in.getUpdateIntervalMs()) {
                in.setUpdateIntervalMs(dto.updateIntervalMs);
                changed = true;
            }

            if (dto.warnAfter != null) {
                try {
                    int secs = convertDuration(dto.warnAfter);
                    if (secs != in.getWarnAfter()) {
                        in.setWarnAfter(secs);
                        changed = true;
                    }
                } catch (IllegalArgumentException e) {
                    call.setCode(422, "Invalid warnAfter value, expected duration");
                    return;
                }
            }

            if (dto.errorAfter != null) {
                try {
                    int secs = convertDuration(dto.errorAfter);
                    if (secs != in.getErrorAfter()) {
                        in.setErrorAfter(secs);
                        changed = true;
                    }
                } catch (IllegalArgumentException e) {
                    call.setCode(422, "Invalid errorAfter value, expected duration");
                    return;
                }
            }

            if (dto.params != null) {
                InputHandler h = handlerFactory.createHandler(in.getType());
                new DataBinder(jsonService).updateMap(true).bind(dto.params, h).check();
                Map<String, Object> params = in.getParams();
                if (params == null) {
                    in.setParams(dto.params);
                    changed = true;
                } else {
                    for (Map.Entry<String, Object> e : dto.params.entrySet()) {
                        String key = e.getKey();
                        Object v = e.getValue();
                        Object existing = params.get(key);
                        if (!v.equals(existing)) {
                            params.put(key, v);
                            changed = true;
                        }
                    }
                }
            }

            if (create) {
                for (int attempt = 0;;) {
                    in.setId(generateId());
                    if (repo.findInput(in.getId()) == null)
                        break;
                    if (++attempt == 20)
                        throw new IOException("Got " + attempt + " dup id's attempting to create input?");
                }
            }

            if (create) {
                q = q.deepCopy(); // make a copy before we modify it
                Map<String, String> inputs = q.getInputs();
                if (inputs == null)
                    q.setInputs(inputs = new HashMap<String, String>());
                inputs.put(id, in.getId());
                repo.updateQueue(q);
            }

            if (changed)
                repo.updateInput(in);
        }
        call.setCode(create ? 201 : 200, createInputDTO(call, id, in));
    }

    @Override
    protected void delete(Call call, String id) throws IOException {
        String inputId = call.getQueue().getInputIdForInput(id);
        if (inputId == null) {
            call.setCode(404);
            return;
        }
        repo.deleteInput(inputId);
    }
}