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

de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.PropertyFieldRegistry Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
/*
 * Copyright (C) 2020 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131
 * Karlsruhe, Germany.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils;

import de.fraunhofer.iosb.ilt.frostserver.model.DefaultEntity;
import de.fraunhofer.iosb.ilt.frostserver.model.EntityChangedMessage;
import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
import de.fraunhofer.iosb.ilt.frostserver.model.core.Entity;
import de.fraunhofer.iosb.ilt.frostserver.model.core.Id;
import de.fraunhofer.iosb.ilt.frostserver.model.ext.TimeInstant;
import de.fraunhofer.iosb.ilt.frostserver.model.ext.TimeInterval;
import de.fraunhofer.iosb.ilt.frostserver.model.ext.TimeValue;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.JsonValue;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.factories.EntityFactories;
import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaMainTable;
import static de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.Utils.getFieldOrNull;
import de.fraunhofer.iosb.ilt.frostserver.property.EntityProperty;
import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyCustomSelect;
import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyMain;
import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain;
import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntity;
import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntitySet;
import de.fraunhofer.iosb.ilt.frostserver.property.Property;
import de.fraunhofer.iosb.ilt.frostserver.util.ParserUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.time4j.Moment;
import org.jooq.Field;
import org.jooq.Record;

/**
 *
 * @author hylke
 * @param  The table type this registry has fields for.
 */
public class PropertyFieldRegistry> {

    private final T table;
    /**
     * The Fields that are allowed be appear in select statements.
     */
    private final Map> epMapSelect;
    /**
     * The Fields that are allowed in where and orderby statements.
     */
    private final Map>> epMapAll;
    /**
     * All select-able fields, by class.
     */
    private final List> allSelectPropertyFields;

    public static interface ExpressionFactory {

        public Field get(T table);
    }

    /**
     * Convert the given Record, holding data from the given Table, into the
     * given Entity.
     *
     * @param  The table type.
     */
    public static interface ConverterRecordRead {

        /**
         * Convert the given Record, holding data from the given Table, into the
         * given Entity. If possible, the data size is added to the DataSize
         * object.
         *
         * @param table The table used to generate the Record.
         * @param input The Record to read the data from.
         * @param entity The entity to write the data to.
         * @param dataSize The DataSize to use to register the amount of data.
         */
        public void convert(T table, Record input, Entity entity, DataSize dataSize);
    }

    public static interface ConverterRecordInsert {

        public void convert(T table, Entity entity, Map insertFields);
    }

    public static interface ConverterRecordUpdate {

        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message);
    }

    public static interface ConverterRecord extends ConverterRecordRead, ConverterRecordInsert, ConverterRecordUpdate {
        // No own methods.
    }

    public static class ConverterRecordDeflt implements ConverterRecord {

        private static final ConverterRecordInsert NULL_INSERT = (table, entity, insertFields) -> {
            // Does nothing
        };
        private static final ConverterRecordUpdate NULL_UPDATE = (table, entity, updateFields, message) -> {
            // Does nothing
        };
        private final ConverterRecordRead read;
        private final ConverterRecordInsert insert;
        private final ConverterRecordUpdate update;

        public ConverterRecordDeflt(ConverterRecordRead read, ConverterRecordInsert insert, ConverterRecordUpdate update) {
            this.read = read;
            this.insert = (insert == null) ? NULL_INSERT : insert;
            this.update = (update == null) ? NULL_UPDATE : update;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            read.convert(table, input, entity, dataSize);
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            insert.convert(table, entity, insertFields);
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            update.convert(table, entity, updateFields, message);
        }

    }

    public static class PropertyFields {

        public final Property property;
        public final boolean jsonType;
        public final Map> fields = new LinkedHashMap<>();
        public final ConverterRecord converter;

        public PropertyFields(Property property, ConverterRecord converter) {
            this(property, false, converter);
        }

        public PropertyFields(Property property, boolean jsonType, ConverterRecord converter) {
            this.property = property;
            this.converter = converter;
            this.jsonType = jsonType;
        }

        public PropertyFields addField(String name, ExpressionFactory field) {
            String key = name;
            if (key == null) {
                key = Integer.toString(fields.size());
            }
            fields.put(key, field);
            return this;
        }
    }

    public static class PropertyFactoryCombo {

        public final Property property;
        public final ExpressionFactory factory;

        public PropertyFactoryCombo(Property property, ExpressionFactory factory) {
            this.property = property;
            this.factory = factory;
        }

    }

    /**
     * A NameFactoryPair for easier passing of a name and a factory.
     *
     * @param  the table type this NFP fetches from.
     */
    public static class NFP {

        public final String name;
        public final ExpressionFactory factory;

        public NFP(String name, ExpressionFactory factory) {
            this.name = name;
            this.factory = factory;
        }
    }

    public PropertyFieldRegistry(T table) {
        this.table = table;
        this.epMapSelect = new HashMap<>();
        this.epMapAll = new HashMap<>();
        this.allSelectPropertyFields = new ArrayList<>();

    }

    public PropertyFieldRegistry(T table, PropertyFieldRegistry copyFrom) {
        this.table = table;
        this.epMapSelect = copyFrom.epMapSelect;
        this.epMapAll = copyFrom.epMapAll;
        this.allSelectPropertyFields = copyFrom.allSelectPropertyFields;
    }

    /**
     * Get the Fields for the given class, that are allowed to be used in the
     * select clause of a query.
     *
     * @param target The list to add to. If null a new list will be created.
     * @return The target list, or a new list if target was null.
     */
    public Collection> getSelectFields(Collection> target) {
        Collection> result = target;
        if (result == null) {
            result = new ArrayList<>();
        }
        result.addAll(allSelectPropertyFields);
        return result;
    }

    /**
     * Get a list of Fields for the given property and table. Add it to the
     * given list, or a new list.
     *
     * @param property The property to get expressions for.
     * @return The target list, or a new list if target was null.
     */
    public PropertyFields getSelectFieldsForProperty(Property property) {
        if (property instanceof EntityPropertyCustomSelect) {
            final EntityPropertyCustomSelect epCustomSelect = (EntityPropertyCustomSelect) property;
            return table.handleEntityPropertyCustomSelect(epCustomSelect);
        } else {
            return epMapSelect.get(property);
        }
    }

    /**
     * Get a Map of expressions for the given property and table. Add it to the
     * given Map, or a new Map.
     *
     * @param property The property to get expressions for.
     * @param target The Map to add to. If null a new Map will be created.
     * @return The target Map, or a new Map if target was null.
     */
    public Map getAllFieldsForProperty(EntityPropertyMain property, Map target) {
        Map> coreMap = epMapAll.get(property);
        if (coreMap == null) {
            throw new IllegalArgumentException("No property called " + property.toString() + " for " + table.getClass());
        }
        Map result = target;
        if (result == null) {
            result = new LinkedHashMap<>();
        }
        for (Map.Entry> es : coreMap.entrySet()) {
            result.put(es.getKey(), es.getValue().get(table));
        }
        return result;
    }

    /**
     * Get the set of expressions for the given set of selected properties.
     *
     * @param selectedProperties The set of properties to get the expressions
     * of.
     * @return The set of expressions.
     */
    public Set> getFieldsForProperties(Set selectedProperties) {
        Set> exprSet = new LinkedHashSet<>();
        if (selectedProperties.isEmpty()) {
            getSelectFields(exprSet);
        } else {
            for (Property property : selectedProperties) {
                final PropertyFields selectFieldsForProperty = getSelectFieldsForProperty(property);
                if (selectFieldsForProperty != null) {
                    exprSet.add(selectFieldsForProperty);
                }
            }
        }
        return exprSet;
    }

    public void addEntry(NavigationPropertyMain property, ExpressionFactory factory) {
        if (property instanceof NavigationPropertyEntity) {
            addEntry((NavigationPropertyEntity) property, factory);
        } else if (property instanceof NavigationPropertyEntitySet) {
            addEntry((NavigationPropertyEntitySet) property, factory);
        } else {
            throw new IllegalArgumentException("Unknown NavigationProperty type: " + property);
        }
    }

    public void addEntry(NavigationPropertyEntity property, ExpressionFactory factory) {
        PropertyFields pf = new PropertyFields<>(property, new ConverterEntity<>(property, factory));
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    public void addEntry(NavigationPropertyEntity property, ExpressionFactory factory, ConverterRecord ps) {
        PropertyFields pf = new PropertyFields<>(property, ps);
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    public void addEntry(NavigationPropertyEntitySet property, ExpressionFactory factory) {
        PropertyFields pf = new PropertyFields<>(property, new ConverterEntitySet<>());
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    public void addEntryString(EntityProperty property, ExpressionFactory factory) {
        PropertyFields pf = new PropertyFields<>(property, new ConverterString<>(property, factory));
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    public void addEntryNumeric(EntityProperty property, ExpressionFactory factory) {
        PropertyFields pf = new PropertyFields<>(property, new ConverterSimple<>(property, factory));
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    public void addEntryId(ExpressionFactory factory) {
        final ConverterId converterId = new ConverterId<>(factory, true);
        addEntry(table.getEntityType().getPrimaryKey(), factory, converterId);
        addEntry(ModelRegistry.EP_SELFLINK, factory, converterId);
    }

    public void addEntryMap(EntityProperty> property, ExpressionFactory factory) {
        PropertyFields pf = new PropertyFields<>(property, true, new ConverterMap<>(property, factory));
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    public void addEntrySimple(EntityProperty property, ExpressionFactory factory) {
        PropertyFields pf = new PropertyFields<>(property, new ConverterSimple<>(property, factory));
        pf.addField(null, factory);
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
        addEntry(epMapAll, property, null, factory);
    }

    /**
     * Add an unnamed entry to the Field registry.
     *
     * @param property The property that this field supplies data for.
     * @param factory The factory to use to generate the Field instance.
     * @param ps The ConverterRecordRead to use to set the get the property from
     * a record and set it on an Entity.
     */
    public void addEntry(Property property, ExpressionFactory factory, ConverterRecord ps) {
        addEntry(property, false, factory, ps);
    }

    public void addEntry(Property property, boolean isJson, ExpressionFactory factory, ConverterRecord ps) {
        PropertyFields pf = new PropertyFields<>(property, isJson, ps);
        if (factory != null) {
            pf.addField(null, factory);
            addEntry(epMapAll, property, null, factory);
        }
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
    }

    /**
     * Add an entry to the Field registry.
     *
     * @param property The property that this field supplies data for.
     * @param ps The ConverterRecordRead used to set the property from a
     * database record.
     * @param factories The factories to use to generate the Field instance used
     * for filter and orderby.
     */
    public void addEntry(Property property, ConverterRecord ps, NFP... factories) {
        addEntry(property, false, ps, factories);
    }

    public void addEntry(Property property, boolean isJson, ConverterRecord ps, NFP... factories) {
        PropertyFields pf = new PropertyFields<>(property, isJson, ps);
        for (NFP nfp : factories) {
            pf.addField(nfp.name, nfp.factory);
            addEntry(epMapAll, property, nfp.name, nfp.factory);
        }
        epMapSelect.put(property, pf);
        allSelectPropertyFields.add(pf);
    }

    /**
     * Add an entry to the Field registry, but do not register it to the entity.
     * This means the field is never used in "select" clauses, but can be used
     * in "filter" clauses.
     *
     * @param property The property that this field supplies data for.
     * @param name The name to use for this field. (j for json, s for string, g
     * for geometry, b for boolean)
     * @param factory The factory to use to generate the Field instance.
     */
    public void addEntryNoSelect(Property property, String name, ExpressionFactory factory) {
        addEntry(epMapAll, property, name, factory);
    }

    private void addEntry(Map>> map, Property property, String name, ExpressionFactory factory) {
        Map> coreMap = map.computeIfAbsent(
                property,
                k -> new LinkedHashMap<>()
        );
        String key = name;
        if (key == null) {
            key = Integer.toString(coreMap.size());
        }
        coreMap.put(key, factory);
    }

    public static class ConverterSimple implements ConverterRecord {

        private final Property property;
        private final ExpressionFactory factory;

        public ConverterSimple(Property property, ExpressionFactory factory) {
            this.property = property;
            this.factory = factory;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            entity.setProperty(property, input.get(factory.get(table)));
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            insertFields.put(factory.get(table), entity.getProperty(property));
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            updateFields.put(factory.get(table), entity.getProperty(property));
            message.addField(property);
        }
    }

    public static class ConverterString implements ConverterRecord {

        private final Property property;
        private final ExpressionFactory factory;

        public ConverterString(Property property, ExpressionFactory factory) {
            this.property = property;
            this.factory = factory;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            String data = (String) input.get(factory.get(table));
            dataSize.increase(data == null ? 0 : data.length());
            entity.setProperty(property, data);
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            insertFields.put(factory.get(table), entity.getProperty(property));
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            updateFields.put(factory.get(table), entity.getProperty(property));
            message.addField(property);
        }
    }

    public static class ConverterTimeInterval implements ConverterRecord {

        private final Property property;
        private final ExpressionFactory factoryStart;
        private final ExpressionFactory factoryEnd;

        public ConverterTimeInterval(Property property, ExpressionFactory factoryStart, ExpressionFactory factoryEnd) {
            this.property = property;
            this.factoryStart = factoryStart;
            this.factoryEnd = factoryEnd;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            entity.setProperty(property, Utils.intervalFromTimes(
                    (Moment) input.get(factoryStart.get(table)),
                    (Moment) input.get(factoryEnd.get(table))));
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            TimeInterval interval = entity.getProperty(property);
            EntityFactories.insertTimeInterval(insertFields, factoryStart.get(table), factoryEnd.get(table), interval);
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            TimeInterval interval = entity.getProperty(property);
            EntityFactories.insertTimeInterval(updateFields, factoryStart.get(table), factoryEnd.get(table), interval);
            message.addField(property);
        }
    }

    public static class ConverterTimeInstant implements ConverterRecord {

        private final Property property;
        private final ExpressionFactory factory;

        public ConverterTimeInstant(Property property, ExpressionFactory factory) {
            this.property = property;
            this.factory = factory;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            entity.setProperty(
                    property,
                    Utils.instantFromTime((Moment) input.get(factory.get(table)))
            );
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            TimeInstant instant = entity.getProperty(property);
            EntityFactories.insertTimeInstant(insertFields, factory.get(table), instant);
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            TimeInstant instant = entity.getProperty(property);
            EntityFactories.insertTimeInstant(updateFields, factory.get(table), instant);
            message.addField(property);
        }
    }

    public static class ConverterTimeValue implements ConverterRecord {

        private final Property property;
        private final ExpressionFactory factoryStart;
        private final ExpressionFactory factoryEnd;

        public ConverterTimeValue(Property property, ExpressionFactory factoryStart, ExpressionFactory factoryEnd) {
            this.property = property;
            this.factoryStart = factoryStart;
            this.factoryEnd = factoryEnd;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            entity.setProperty(
                    property,
                    Utils.valueFromTimes(
                            (Moment) input.get(factoryStart.get(table)),
                            (Moment) input.get(factoryEnd.get(table))
                    )
            );
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            TimeValue value = entity.getProperty(property);
            EntityFactories.insertTimeValue(insertFields, factoryStart.get(table), factoryEnd.get(table), value);
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            TimeValue value = entity.getProperty(property);
            EntityFactories.insertTimeValue(updateFields, factoryStart.get(table), factoryEnd.get(table), value);
            message.addField(property);
        }
    }

    public static class ConverterMap implements ConverterRecord {

        private final Property property;
        private final ExpressionFactory factory;

        public ConverterMap(Property property, ExpressionFactory factory) {
            this.property = property;
            this.factory = factory;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            JsonValue data = Utils.getFieldJsonValue(input, factory.get(table));
            if (data == null) {
                return;
            }
            dataSize.increase(data.getStringLength());
            entity.setProperty(property, data.getMapValue());
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            insertFields.put(factory.get(table), new JsonValue(entity.getProperty(property)));
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            updateFields.put(factory.get(table), new JsonValue(entity.getProperty(property)));
            message.addField(property);
        }
    }

    public static class ConverterId implements ConverterRecord {

        private final ExpressionFactory factory;
        private final boolean canEdit;

        public ConverterId(ExpressionFactory factory, boolean canEdit) {
            this.factory = factory;
            this.canEdit = canEdit;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            final Object rawId = getFieldOrNull(input, factory.get(table));
            Id id = ParserUtils.idFromObject(rawId);
            entity.setProperty(entity.getEntityType().getPrimaryKey(), id);
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            if (!canEdit) {
                return;
            }
            Id id = entity.getProperty(entity.getEntityType().getPrimaryKey());
            if (id == null) {
                return;
            }
            insertFields.put(factory.get(table), id.getValue());
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            // ID is never updated.
        }
    }

    public static class ConverterEntity implements ConverterRecord {

        private final NavigationPropertyMain property;
        private final ExpressionFactory factory;

        public ConverterEntity(NavigationPropertyMain property, ExpressionFactory factory) {
            this.property = property;
            this.factory = factory;
        }

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            final Object rawId = getFieldOrNull(input, factory.get(table));
            if (rawId == null) {
                return;
            }
            DefaultEntity childEntity = new DefaultEntity(property.getEntityType(), ParserUtils.idFromObject(rawId));
            entity.setProperty(property, childEntity);
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            Entity child = entity.getProperty(property);
            insertFields.put(factory.get(table), child.getId().getValue());
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            Entity child = entity.getProperty(property);
            updateFields.put(factory.get(table), child.getId().getValue());
            message.addField(property);
        }
    }

    public static class ConverterEntitySet implements ConverterRecord {

        @Override
        public void convert(T table, Record input, Entity entity, DataSize dataSize) {
            // EntitySet properties are not fetched in this way
        }

        @Override
        public void convert(T table, Entity entity, Map insertFields) {
            // EntitySet properties are not created in this way
        }

        @Override
        public void convert(T table, Entity entity, Map updateFields, EntityChangedMessage message) {
            // EntitySet properties are not updated in this way
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy