All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog2.inputs.InputServiceImpl Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * 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
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.inputs;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.eventbus.EventBus;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.bson.types.ObjectId;
import org.graylog2.database.MongoConnection;
import org.graylog2.database.NotFoundException;
import org.graylog2.database.PersistedServiceImpl;
import org.graylog2.events.ClusterEventBus;
import org.graylog2.inputs.converters.ConverterFactory;
import org.graylog2.inputs.encryption.EncryptedInputConfigs;
import org.graylog2.inputs.extractors.ExtractorFactory;
import org.graylog2.inputs.extractors.events.ExtractorCreated;
import org.graylog2.inputs.extractors.events.ExtractorDeleted;
import org.graylog2.inputs.extractors.events.ExtractorUpdated;
import org.graylog2.jackson.TypeReferences;
import org.graylog2.plugin.configuration.Configuration;
import org.graylog2.plugin.database.EmbeddedPersistable;
import org.graylog2.plugin.database.Persisted;
import org.graylog2.plugin.database.ValidationException;
import org.graylog2.plugin.inputs.Converter;
import org.graylog2.plugin.inputs.Extractor;
import org.graylog2.plugin.inputs.MessageInput;
import org.graylog2.rest.models.system.inputs.responses.InputCreated;
import org.graylog2.rest.models.system.inputs.responses.InputDeleted;
import org.graylog2.rest.models.system.inputs.responses.InputUpdated;
import org.graylog2.security.encryption.EncryptedValue;
import org.graylog2.security.encryption.EncryptedValueMapperConfig;
import org.graylog2.shared.inputs.MessageInputFactory;
import org.graylog2.shared.inputs.NoSuchInputTypeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class InputServiceImpl extends PersistedServiceImpl implements InputService {
    private static final Logger LOG = LoggerFactory.getLogger(InputServiceImpl.class);

    private final ExtractorFactory extractorFactory;
    private final ConverterFactory converterFactory;
    private final MessageInputFactory messageInputFactory;
    private final EventBus clusterEventBus;
    private final DBCollection dbCollection;
    private final ObjectMapper objectMapper;

    @Inject
    public InputServiceImpl(MongoConnection mongoConnection,
                            ExtractorFactory extractorFactory,
                            ConverterFactory converterFactory,
                            MessageInputFactory messageInputFactory,
                            ClusterEventBus clusterEventBus,
                            ObjectMapper objectMapper) {
        super(mongoConnection);
        this.extractorFactory = extractorFactory;
        this.converterFactory = converterFactory;
        this.messageInputFactory = messageInputFactory;
        this.clusterEventBus = clusterEventBus;
        this.dbCollection = collection(InputImpl.class);
        this.objectMapper = objectMapper.copy();
        EncryptedValueMapperConfig.enableDatabase(this.objectMapper);
    }

    @Override
    public List all() {
        final List ownInputs = query(InputImpl.class, new BasicDBObject());

        final ImmutableList.Builder inputs = ImmutableList.builder();
        for (final DBObject o : ownInputs) {
            inputs.add(createFromDbObject(o));
        }

        return inputs.build();
    }

    @Override
    public List allOfThisNode(final String nodeId) {
        final List query = ImmutableList.of(
                new BasicDBObject(MessageInput.FIELD_NODE_ID, nodeId),
                new BasicDBObject(MessageInput.FIELD_GLOBAL, true));
        final List ownInputs = query(InputImpl.class, new BasicDBObject("$or", query));

        final ImmutableList.Builder inputs = ImmutableList.builder();
        for (final DBObject o : ownInputs) {
            inputs.add(createFromDbObject(o));
        }

        return inputs.build();
    }

    @Override
    public List allByType(final String type) {
        final ImmutableList.Builder inputs = ImmutableList.builder();
        for (final DBObject o : query(InputImpl.class, new BasicDBObject(MessageInput.FIELD_TYPE, type))) {
            inputs.add(createFromDbObject(o));
        }
        return inputs.build();
    }

    @Override
    public Set findByIds(Collection ids) {
        final Set objectIds = ids.stream()
                .map(ObjectId::new)
                .collect(Collectors.toSet());

        final DBObject query = BasicDBObjectBuilder.start()
                .push(InputImpl.FIELD_ID)
                .append("$in", objectIds)
                .get();
        final Stream inputStream = query(InputImpl.class, query).stream()
                .map(o -> createFromDbObject(o));
        return inputStream
                .collect(Collectors.toSet());
    }

    @Override
    public  String save(T model) throws ValidationException {
        return save(model, true);
    }

    @Override
    public  String saveWithoutEvents(T model) throws ValidationException {
        return save(model, false);
    }

    private  String save(T model, boolean fireEvents) throws ValidationException {
        final String resultId = super.save(model);
        if (resultId != null && !resultId.isEmpty() && fireEvents) {
            publishChange(InputCreated.create(resultId));
        }
        return resultId;
    }

    @Override
    public String update(Input model) throws ValidationException {
        final String resultId = super.save(model);
        if (resultId != null && !resultId.isEmpty()) {
            publishChange(InputUpdated.create(resultId));
        }
        return resultId;
    }

    @Override
    public  String saveWithoutValidation(T model) {
        final String resultId = super.saveWithoutValidation(model);
        if (resultId != null && !resultId.isEmpty()) {
            publishChange(InputCreated.create(resultId));
        }
        return resultId;
    }

    @Override
    public  int destroy(T model) {
        final int result = super.destroy(model);
        if (result > 0) {
            publishChange(InputDeleted.create(model.getId()));
        }
        return result;
    }

    @Override
    public Input create(String id, Map fields) {
        return new InputImpl(new ObjectId(id), fields);
    }

    @Override
    public Input create(Map fields) {
        return new InputImpl(fields);
    }

    @Override
    public Input find(String id) throws NotFoundException {
        if (!ObjectId.isValid(id)) {
            throw new NotFoundException("Input id <" + id + "> is invalid!");
        }
        final DBObject o = get(org.graylog2.inputs.InputImpl.class, id);
        if (o == null) {
            throw new NotFoundException("Input <" + id + "> not found!");
        }
        return createFromDbObject(o);
    }

    @Override
    public Input findForThisNodeOrGlobal(String nodeId, String id) throws NotFoundException {
        final List forThisNodeOrGlobal = ImmutableList.of(
                new BasicDBObject(MessageInput.FIELD_NODE_ID, nodeId),
                new BasicDBObject(MessageInput.FIELD_GLOBAL, true));

        final List query = ImmutableList.of(
                new BasicDBObject(InputImpl.FIELD_ID, new ObjectId(id)),
                new BasicDBObject("$or", forThisNodeOrGlobal));

        final DBObject o = findOne(InputImpl.class, new BasicDBObject("$and", query));
        return createFromDbObject(o);
    }

    @Override
    public Input findForThisNode(String nodeId, String id) throws NotFoundException, IllegalArgumentException {
        final List forThisNode = ImmutableList.of(
                new BasicDBObject(MessageInput.FIELD_NODE_ID, nodeId),
                new BasicDBObject(MessageInput.FIELD_GLOBAL, false));

        final List query = ImmutableList.of(
                new BasicDBObject(InputImpl.FIELD_ID, new ObjectId(id)),
                new BasicDBObject("$and", forThisNode));

        final DBObject o = findOne(InputImpl.class, new BasicDBObject("$and", query));
        if (o == null) {
            throw new NotFoundException("Couldn't find input " + id + " on Graylog node " + nodeId);
        } else {
            return createFromDbObject(o);
        }
    }

    @Override
    public void addExtractor(Input input, Extractor extractor) throws ValidationException {
        embed(input, InputImpl.EMBEDDED_EXTRACTORS, extractor);
        publishChange(ExtractorCreated.create(input.getId(), extractor.getId()));
    }

    @Override
    public void updateExtractor(Input input, Extractor extractor) throws ValidationException {
        removeEmbedded(input, InputImpl.EMBEDDED_EXTRACTORS, extractor.getId());
        embed(input, InputImpl.EMBEDDED_EXTRACTORS, extractor);
        publishChange(ExtractorUpdated.create(input.getId(), extractor.getId()));
    }

    @Override
    public void addStaticField(Input input, final String key, final String value) throws ValidationException {
        final EmbeddedPersistable obj = () -> ImmutableMap.of(
                InputImpl.FIELD_STATIC_FIELD_KEY, key,
                InputImpl.FIELD_STATIC_FIELD_VALUE, value);

        embed(input, InputImpl.EMBEDDED_STATIC_FIELDS, obj);
        publishChange(InputUpdated.create(input.getId()));
    }

    @Override
    public List> getStaticFields(Input input) {
        if (input.getFields().get(InputImpl.EMBEDDED_STATIC_FIELDS) == null) {
            return Collections.emptyList();
        }

        final ImmutableList.Builder> listBuilder = ImmutableList.builder();
        final BasicDBList mSF = (BasicDBList) input.getFields().get(InputImpl.EMBEDDED_STATIC_FIELDS);
        for (final Object element : mSF) {
            final DBObject ex = (BasicDBObject) element;
            try {
                final Map.Entry staticField =
                        Maps.immutableEntry((String) ex.get(InputImpl.FIELD_STATIC_FIELD_KEY),
                                (String) ex.get(InputImpl.FIELD_STATIC_FIELD_VALUE));
                listBuilder.add(staticField);
            } catch (Exception e) {
                LOG.error("Cannot build static field from persisted data. Skipping.", e);
            }
        }

        return listBuilder.build();
    }

    @Override
    @SuppressWarnings("unchecked")
    public List getExtractors(Input input) {
        if (input.getFields().get(InputImpl.EMBEDDED_EXTRACTORS) == null) {
            return Collections.emptyList();
        }

        final ImmutableList.Builder listBuilder = ImmutableList.builder();
        final BasicDBList mEx = (BasicDBList) input.getFields().get(InputImpl.EMBEDDED_EXTRACTORS);
        for (final Object element : mEx) {
            final DBObject ex = (BasicDBObject) element;

            // SOFT MIGRATION: does this extractor have an order set? Implemented for issue: #726
            Long order = 0L;
            if (ex.containsField(Extractor.FIELD_ORDER)) {
                /* We use json format to describe our test fixtures
                   This format will only return Integer on this place,
                   which can't be converted to long. So I first cast
                   it to Number and eventually to long */
                Number num = (Number) ex.get(Extractor.FIELD_ORDER);
                order = num.longValue(); // mongodb driver gives us a java.lang.Long
            }

            try {
                final Extractor extractor = extractorFactory.factory(
                        (String) ex.get(Extractor.FIELD_ID),
                        (String) ex.get(Extractor.FIELD_TITLE),
                        order.intValue(),
                        Extractor.CursorStrategy.valueOf(((String) ex.get(Extractor.FIELD_CURSOR_STRATEGY)).toUpperCase(Locale.ENGLISH)),
                        Extractor.Type.valueOf(((String) ex.get(Extractor.FIELD_TYPE)).toUpperCase(Locale.ENGLISH)),
                        (String) ex.get(Extractor.FIELD_SOURCE_FIELD),
                        (String) ex.get(Extractor.FIELD_TARGET_FIELD),
                        (Map) ex.get(Extractor.FIELD_EXTRACTOR_CONFIG),
                        (String) ex.get(Extractor.FIELD_CREATOR_USER_ID),
                        getConvertersOfExtractor(ex),
                        Extractor.ConditionType.valueOf(((String) ex.get(Extractor.FIELD_CONDITION_TYPE)).toUpperCase(Locale.ENGLISH)),
                        (String) ex.get(Extractor.FIELD_CONDITION_VALUE)
                );

                listBuilder.add(extractor);
            } catch (Exception e) {
                LOG.error("Cannot build extractor from persisted data. Skipping.", e);
            }
        }

        return listBuilder.build();
    }

    @Override
    public Extractor getExtractor(final Input input, final String extractorId) throws NotFoundException {
        final Optional extractor = Iterables.tryFind(this.getExtractors(input), new Predicate<>() {
            @Override
            public boolean apply(Extractor extractor) {
                return extractor.getId().equals(extractorId);
            }
        });

        if (!extractor.isPresent()) {
            LOG.error("Extractor <{}> not found.", extractorId);
            throw new NotFoundException("Couldn't find extractor " + extractorId);
        }

        return extractor.get();
    }

    @SuppressWarnings("unchecked")
    private List getConvertersOfExtractor(DBObject extractor) {
        final ImmutableList.Builder listBuilder = ImmutableList.builder();

        final BasicDBList converters = (BasicDBList) extractor.get(Extractor.FIELD_CONVERTERS);
        for (final Object element : converters) {
            final DBObject c = (BasicDBObject) element;

            try {
                listBuilder.add(converterFactory.create(
                        Converter.Type.valueOf(((String) c.get(Extractor.FIELD_CONVERTER_TYPE)).toUpperCase(Locale.ENGLISH)),
                        (Map) c.get(Extractor.FIELD_CONVERTER_CONFIG)
                ));
            } catch (ConverterFactory.NoSuchConverterException e1) {
                LOG.error("Cannot build converter from persisted data. No such converter.", e1);
            } catch (Exception e) {
                LOG.error("Cannot build converter from persisted data.", e);
            }
        }

        return listBuilder.build();
    }

    @Override
    public void removeExtractor(Input input, String extractorId) {
        removeEmbedded(input, InputImpl.EMBEDDED_EXTRACTORS, extractorId);
        publishChange(ExtractorDeleted.create(input.getId(), extractorId));
    }

    @Override
    public void removeStaticField(Input input, String key) {
        removeEmbedded(input, InputImpl.FIELD_STATIC_FIELD_KEY, InputImpl.EMBEDDED_STATIC_FIELDS, key);
        publishChange(InputUpdated.create(input.getId()));
    }

    @Override
    public MessageInput getMessageInput(Input io) throws NoSuchInputTypeException {
        final Configuration configuration = new Configuration(io.getConfiguration());
        final MessageInput input = messageInputFactory.create(io.getType(), configuration);

        // Add all standard fields.
        input.setTitle(io.getTitle());
        input.setNodeId(io.getNodeId());
        input.setCreatorUserId(io.getCreatorUserId());
        input.setPersistId(io.getId());
        input.setCreatedAt(io.getCreatedAt());
        input.setContentPack(io.getContentPack());
        input.setDesiredState(io.getDesiredState());

        if (io.isGlobal()) {
            input.setGlobal(true);
        }

        // Add static fields.
        input.addStaticFields(io.getStaticFields());

        return input;
    }

    @Override
    public long totalCount() {
        return totalCount(InputImpl.class);
    }

    @Override
    public long globalCount() {
        return count(InputImpl.class, new BasicDBObject(MessageInput.FIELD_GLOBAL, true));
    }

    @Override
    public long localCount() {
        return count(InputImpl.class, new BasicDBObject(MessageInput.FIELD_GLOBAL, false));
    }

    @Override
    public Map totalCountByType() {
        final Map inputCountByType = new HashMap<>();
        try (DBCursor inputTypes = dbCollection.find(null, new BasicDBObject(MessageInput.FIELD_TYPE, 1))) {
            for (DBObject inputType : inputTypes) {
                final String type = (String) inputType.get(MessageInput.FIELD_TYPE);
                if (type != null) {
                    final Long oldValue = inputCountByType.get(type);
                    final Long newValue = (oldValue == null) ? 1 : oldValue + 1;
                    inputCountByType.put(type, newValue);
                }
            }
        }

        return inputCountByType;
    }

    @Override
    public long localCountForNode(String nodeId) {
        final List forThisNode = ImmutableList.of(new BasicDBObject(MessageInput.FIELD_NODE_ID, nodeId));

        final List query = ImmutableList.of(
                new BasicDBObject(MessageInput.FIELD_GLOBAL, false),
                new BasicDBObject("$or", forThisNode));

        return count(InputImpl.class, new BasicDBObject("$and", query));
    }

    @Override
    public long totalCountForNode(String nodeId) {
        final List query = ImmutableList.of(
                new BasicDBObject(MessageInput.FIELD_GLOBAL, true),
                new BasicDBObject(MessageInput.FIELD_NODE_ID, nodeId));

        return count(InputImpl.class, new BasicDBObject("$or", query));
    }

    @Override
    public long totalExtractorCount() {
        final DBObject query = new BasicDBObject(InputImpl.EMBEDDED_EXTRACTORS, new BasicDBObject("$exists", true));
        long extractorsCount = 0;
        try (DBCursor inputs = dbCollection.find(query, new BasicDBObject(InputImpl.EMBEDDED_EXTRACTORS, 1))) {
            for (DBObject input : inputs) {
                final BasicDBList extractors = (BasicDBList) input.get(InputImpl.EMBEDDED_EXTRACTORS);
                extractorsCount += extractors.size();
            }
        }

        return extractorsCount;
    }

    @Override
    public Map totalExtractorCountByType() {
        final DBObject query = new BasicDBObject(InputImpl.EMBEDDED_EXTRACTORS, new BasicDBObject("$exists", true));
        try (DBCursor inputs = dbCollection.find(query, new BasicDBObject(InputImpl.EMBEDDED_EXTRACTORS, 1))) {
            final Map extractorsCountByType = new HashMap<>();
            for (DBObject input : inputs) {
                final BasicDBList extractors = (BasicDBList) input.get(InputImpl.EMBEDDED_EXTRACTORS);
                for (Object dbObject : extractors) {
                    final DBObject extractor = (DBObject) dbObject;
                    final Extractor.Type type = Extractor.Type.fuzzyValueOf(((String) extractor.get(Extractor.FIELD_TYPE)));
                    if (type != null) {
                        final Long oldValue = extractorsCountByType.get(type);
                        final Long newValue = (oldValue == null) ? 1 : oldValue + 1;
                        extractorsCountByType.put(type, newValue);
                    }
                }
            }
            return extractorsCountByType;
        }
    }

    private void publishChange(Object event) {
        this.clusterEventBus.post(event);
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private InputImpl createFromDbObject(DBObject o) {
        final Map inputMap = new HashMap<>(o.toMap());

        final String type = (String) inputMap.get(MessageInput.FIELD_TYPE);
        final var encryptedFields = getEncryptedFields(type);

        if (encryptedFields.isEmpty()) {
            return new InputImpl((ObjectId) inputMap.get(InputImpl.FIELD_ID), inputMap);
        }

        final Map config = new HashMap<>((Map) inputMap.get(MessageInput.FIELD_CONFIGURATION));
        encryptedFields.forEach(field -> {
            final var encryptedValue = objectMapper.convertValue(config.get(field), EncryptedValue.class);
            config.put(field, encryptedValue);
        });

        inputMap.put(MessageInput.FIELD_CONFIGURATION, config);

        return new InputImpl((ObjectId) inputMap.get(InputImpl.FIELD_ID), inputMap);
    }

    private Set getEncryptedFields(String type) {
        return messageInputFactory.getConfig(type)
                .map(EncryptedInputConfigs::getEncryptedFields)
                .orElse(Set.of());
    }

    @Override
    protected void fieldTransformations(Map doc) {
        for (Map.Entry x : doc.entrySet()) {
            if (x.getValue() instanceof EncryptedValue encryptedValue) {
                doc.put(x.getKey(), objectMapper.convertValue(encryptedValue, TypeReferences.MAP_STRING_OBJECT));
                return;
            }
        }
        super.fieldTransformations(doc);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy