package com.linbit.linstor.dbdrivers; import com.linbit.ImplementationError; import com.linbit.InvalidIpAddressException; import com.linbit.InvalidNameException; import com.linbit.ValueOutOfRangeException; import com.linbit.drbd.md.MdException; import com.linbit.linstor.LinStorDBRuntimeException; import com.linbit.linstor.dbdrivers.DatabaseDriverInfo.DatabaseType; import com.linbit.linstor.dbdrivers.DatabaseTable.Column; import com.linbit.linstor.dbdrivers.etcd.ETCDEngine; import com.linbit.linstor.dbdrivers.interfaces.GenericDatabaseDriver; import com.linbit.linstor.dbdrivers.interfaces.updater.CollectionDatabaseDriver; import com.linbit.linstor.dbdrivers.interfaces.updater.SingleColumnDatabaseDriver; import com.linbit.linstor.dbdrivers.sql.SQLEngine; import com.linbit.linstor.logging.ErrorReporter; import com.linbit.linstor.security.AccessDeniedException; import com.linbit.linstor.security.ObjectProtection; import com.linbit.linstor.security.ObjectProtectionDatabaseDriver; import com.linbit.linstor.stateflags.Flags; import com.linbit.linstor.stateflags.StateFlagsPersistence; import com.linbit.utils.ExceptionThrowingFunction; import com.linbit.utils.Pair; import java.io.IOException; import java.sql.JDBCType; import java.sql.Types; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public abstract class AbsDatabaseDriver<DATA, INIT_MAPS, LOAD_ALL> implements GenericDatabaseDriver<DATA>, ControllerDatabaseDriver<DATA, INIT_MAPS, LOAD_ALL> { protected static final ObjectMapper OBJ_MAPPER = new ObjectMapper(); private final ErrorReporter errorReporter; private final DatabaseTable table; private final DbEngine dbEngine; private final ObjectProtectionDatabaseDriver objProtDriver; private final Map<Column, ExceptionThrowingFunction<DATA, Object, AccessDeniedException>> setters; public AbsDatabaseDriver( ErrorReporter errorReporterRef, DatabaseTable tableRef, DbEngine dbEngineRef, ObjectProtectionDatabaseDriver objProtDriverRef ) { errorReporter = errorReporterRef; table = tableRef; dbEngine = dbEngineRef; objProtDriver = objProtDriverRef; setters = new HashMap<>(); } @Override public void create(DATA dataRef) throws DatabaseException { try { dbEngine.create(setters, dataRef, table, this::getId); } catch (AccessDeniedException exc) { throw new ImplementationError("Database driver does not have enough privileges"); } } @Override public void delete(DATA dataRef) throws DatabaseException { try { dbEngine.delete(setters, dataRef, table, this::getId); } catch (AccessDeniedException exc) { throw new ImplementationError("Database driver does not have enough privileges"); } } @Override public Map<DATA, INIT_MAPS> loadAll(LOAD_ALL parentRef) throws DatabaseException { // fail fast is not configured correctly performSanityCheck(); errorReporter.logTrace("Loading all %ss", table.getName()); Map<DATA, INIT_MAPS> loadedObjectsMap; try { loadedObjectsMap = dbEngine.loadAll(table, parentRef, this::load); } catch (AccessDeniedException exc) { throw new ImplementationError("Database context does not have enough privileges"); } catch (InvalidNameException | InvalidIpAddressException | ValueOutOfRangeException | MdException exc) { // TODO improve exception-handling throw new DatabaseException("Failed to restore data", exc); } errorReporter.logTrace("Loaded %d %ss", loadedObjectsMap.size(), table.getName()); return loadedObjectsMap; } public void performSanityCheck() { for (Column col : table.values()) { if (!setters.containsKey(col)) { throw new ImplementationError( "Missing column-setter for " + table.getName() + "." + col.getName() + " in " + this.getClass().getSimpleName() ); } } } protected <FLAG extends Enum<FLAG> & Flags> StateFlagsPersistence<DATA> generateFlagDriver( Column col, Class<FLAG> flagsClass ) { return dbEngine.generateFlagsDriver(setters, col, flagsClass, this::getId); } protected <INPUT_TYPE, DB_TYPE> SingleColumnDatabaseDriver<DATA, INPUT_TYPE> generateSingleColumnDriver( Column col, ExceptionThrowingFunction<DATA, String, AccessDeniedException> dataValueToString, Function<INPUT_TYPE, DB_TYPE> typeMapper ) { return dbEngine.generateSingleColumnDriver( setters, col, typeMapper, this::getId, dataValueToString ); } protected <LIST_TYPE> CollectionDatabaseDriver<DATA, LIST_TYPE> generateCollectionToJsonStringArrayDriver( Column col ) { return dbEngine.generateCollectionToJsonStringArrayDriver(setters, col, this::getId); } protected void setColumnSetter( Column colRef, ExceptionThrowingFunction<DATA, Object, AccessDeniedException> setterRef ) { setters.put(colRef, setterRef); } protected ObjectProtection getObjectProtection(String objProtPath) throws DatabaseException { ObjectProtection objProt = objProtDriver.loadObjectProtection( objProtPath, false // no need to log a warning, as we would fail then anyways ); if (objProt == null) { throw new ImplementationError( table.getName() + "'s DB entry exists, but is missing an entry in ObjProt table! " + objProtPath, null ); } return objProt; } protected DatabaseType getDbType() { return dbEngine.getType(); } protected String toString(List<?> asStrListRef) throws LinStorDBRuntimeException { try { return OBJ_MAPPER.writeValueAsString(asStrListRef); } catch (JsonProcessingException exc) { throw new LinStorDBRuntimeException("Failed to write json array"); } } protected byte[] toBlob(List<?> asStrListRef) throws LinStorDBRuntimeException { try { return OBJ_MAPPER.writeValueAsBytes(asStrListRef); } catch (JsonProcessingException exc) { throw new LinStorDBRuntimeException("Failed to write json array"); } } protected abstract Pair<DATA, INIT_MAPS> load( RawParameters raw, LOAD_ALL parentRef ) throws DatabaseException, InvalidNameException, ValueOutOfRangeException, InvalidIpAddressException, MdException; protected abstract String getId(DATA data) throws AccessDeniedException; /** * This class is basically only a wrapper for an Object[]. {@link ETCDEngine} or {@link SQLEngine} * already have written their java-raw types in this Object[] ({@link ETCDEngine} only {@link String}s * but {@link SQLEngine} the types defined in {@link GeneratedDatabaseTables}). */ public static class RawParameters { private final DatabaseTable table; private final Map<String, Object> rawParameters; public RawParameters(DatabaseTable tableRef, Map<String, Object> rawParametersRef) { table = tableRef; rawParameters = rawParametersRef; } @SuppressWarnings("unchecked") public <T> T get(Column col) { return (T) rawParameters.get(col.getName()); } public <T, R, EXC extends Exception> R build( Column col, ExceptionThrowingFunction<T, R, EXC> func ) throws EXC { T data = get(col); R ret = null; if (data != null) { ret = func.accept(data); } return ret; } public <R extends Enum<R>> R build(Column col, Class<R> eType) throws IllegalArgumentException { String data = get(col); R ret = null; if (data != null) { ret = Enum.valueOf(eType, data); } return ret; } public List<String> getAsStringList(Column col) throws DatabaseException { List<String> ret; try { Object value = get(col); if (value == null) { ret = null; } else if (col.getSqlType() == Types.VARCHAR) { ret = new ArrayList<>(OBJ_MAPPER.readValue((String) value, List.class)); } else if (col.getSqlType() == Types.BLOB) { ret = new ArrayList<>(OBJ_MAPPER.readValue((byte[]) value, List.class)); } else { throw new DatabaseException( "Failed to deserialize json array. No handler found for sql type: " + JDBCType.valueOf(col.getSqlType()) + " in table " + table.getName() + ", column " + col.getName() ); } } catch (IOException exc) { throw new DatabaseException( "Failed to deserialize json array. Table: " + table.getName() + ", column: " + col.getName(), exc ); } return ret; } } }