diff --git a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/AColumn.java b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/AColumn.java new file mode 100644 index 000000000..35c0cb152 --- /dev/null +++ b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/AColumn.java @@ -0,0 +1,11 @@ +package com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface AColumn { +} diff --git a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/ATable.java b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/ATable.java new file mode 100644 index 000000000..8fc6c2057 --- /dev/null +++ b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/ATable.java @@ -0,0 +1,11 @@ +package com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ATable { +} diff --git a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/Persistable.java b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/Persistable.java index 1d682551e..28a164b18 100644 --- a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/Persistable.java +++ b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/Persistable.java @@ -20,6 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.gson.JsonElement; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.util.List; import java.util.Map; @@ -48,6 +49,9 @@ public interface Persistable { */ JsonElement read(String rca); + T read(Class object) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException; + /** * Write data to the database. * @@ -56,6 +60,8 @@ public interface Persistable { */ void write(Node node, T flowUnit) throws SQLException, IOException; + long write(T object) throws Exception; + void close() throws SQLException; /** diff --git a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/PersistorBase.java b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/PersistorBase.java index 509a6e4f9..f752b6aad 100644 --- a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/PersistorBase.java +++ b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/PersistorBase.java @@ -23,6 +23,7 @@ import com.google.gson.JsonElement; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; @@ -188,6 +189,28 @@ public synchronized void write(Node node, T flow } } + public synchronized long write(T obj) throws Exception { + rotateRegisterGarbageThenCreateNewDB(RotationType.TRY_ROTATE); + try { + return writeImpl(obj); + } catch (IllegalStateException | IllegalArgumentException illegalEx) { + throw illegalEx; + } + catch (Exception e) { + LOG.info("RCA: Fail to write.", e); + rotateRegisterGarbageThenCreateNewDB(RotationType.FORCE_ROTATE); + try { + return writeImpl(obj); + } catch (Exception ex) { + LOG.error("Failed to write multiple times. Giving up."); + // We rethrow this exception so that framework can take appropriate action. + throw e; + } + } + } + + abstract long writeImpl(T obj) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SQLException; + private synchronized void rotateRegisterGarbageThenCreateNewDB(RotationType type) throws IOException, SQLException { Path rotatedFile = null; long currTime = System.currentTimeMillis(); diff --git a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SQLitePersistor.java b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SQLitePersistor.java index 2df441577..37bef50c8 100644 --- a/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SQLitePersistor.java +++ b/src/main/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SQLitePersistor.java @@ -18,7 +18,6 @@ import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.Resources.State; import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.flow_units.ResourceFlowUnit; import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.flow_units.ResourceFlowUnit.ResourceFlowUnitFieldValue; -import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.summaries.SummaryBuilder; import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.summaries.temperature.ClusterTemperatureSummary; import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.summaries.temperature.CompactNodeSummary; import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.framework.api.summaries.temperature.NodeLevelDimensionalSummary; @@ -34,17 +33,24 @@ import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -54,21 +60,54 @@ import org.jooq.InsertValuesStepN; import org.jooq.JSONFormat; import org.jooq.Record; -import org.jooq.Record1; import org.jooq.Result; import org.jooq.SQLDialect; import org.jooq.SelectJoinStep; import org.jooq.Table; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; +import org.jooq.impl.ParserException; class SQLitePersistor extends PersistorBase { + private enum GetterOrSetter { + GETTER, + SETTER, + NEITHER + } + + private static class GetterSetterPairs { + Method getter = null; + Method setter = null; + } + + private static class ColumnValuePair { + Field field; + Object value; + } + private static final String DB_URL = "jdbc:sqlite:"; private DSLContext create; private Map>> jooqTableColumns; private static final Logger LOG = LogManager.getLogger(SQLitePersistor.class); private static final String LAST_INSERT_ROWID = "last_insert_rowid()"; private static final String PRIMARY_KEY_AUTOINCREMENT_POSTFIX = " INTEGER PRIMARY KEY AUTOINCREMENT"; + private Map> tableNameToJavaClassMap; + + private static final String[] GETTER_PREFIXES = {"get", "is"}; + private static final String[] SETTER_PREFIXES = {"set"}; + + /** + * This is for efficient lookup of the getter and setter methods for all the persistable Fields of a class. This map can be in-memory and + * does not need to be re-created during DB file rotations as we don't support dynamic class loading and rotating the DB files should + * not change the members of the class. + */ + private Map, Map> fieldGetterSetterPairsMap; + + private Map, Map> classFieldNamesToGetterSetterMap; + + // When persisting an object in the DB, is a getter for the Object is annotated with @AColumn and @ATable, then the return type Object is + // persisted in a a different table and the primary key of the other table is persisted as a pointer in the outer object table. + private static final String NESTED_OBJECT_COLUMN_PREFIX = "__table__"; private static int id_test = 1; @@ -77,6 +116,9 @@ class SQLitePersistor extends PersistorBase { super(dir, filename, DB_URL, storageFileRetentionCount, rotationTime, rotationPeriod); create = DSL.using(conn, SQLDialect.SQLITE); jooqTableColumns = new HashMap<>(); + tableNameToJavaClassMap = new HashMap<>(); + this.fieldGetterSetterPairsMap = new HashMap<>(); + this.classFieldNamesToGetterSetterMap = new HashMap<>(); } // This updates the DSL context based on a new SQLite connection @@ -88,6 +130,7 @@ synchronized void createNewDSLContext() { } create = DSL.using(super.conn, SQLDialect.SQLITE); jooqTableColumns = new HashMap<>(); + tableNameToJavaClassMap = new HashMap<>(); } @Override @@ -166,7 +209,7 @@ synchronized int insertRow(String tableName, List row) throws SQLExcepti .insertInto(table) .columns(columnsForTable) .values(row); - + try { insertValuesStepN.execute(); LOG.debug("sql insert: {}", insertValuesStepN.toString()); @@ -179,6 +222,327 @@ synchronized int insertRow(String tableName, List row) throws SQLExcepti return lastPrimaryKey; } + @Override + public T read(Class clz) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException { + return read(clz, -1); + } + + public T read(Class clz, int rowId) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException { + String tableName = getTableNameFromClassName(clz); + String primaryKeyCol = SQLiteQueryUtils.getPrimaryKeyColumnName(tableName); + Field primaryKeyField = DSL.field(primaryKeyCol, Integer.class); + + Map fieldNameToGetterSetterMap = classFieldNamesToGetterSetterMap.get(clz); + + + List recordList; + if (rowId == -1) { + // Fetch the latest row. + recordList = create.select().from(tableName).orderBy(primaryKeyField.desc()).limit(1).fetch(); + } else { + recordList = create.select().from(tableName).where(DSL.field(primaryKeyCol, Integer.class).eq(rowId)).fetch(); + } + + if (recordList.size() != 1) { + throw new IllegalStateException(); + } + Record record = recordList.get(0); + + Field[] fields = record.fields(); + // Map methodMap = Arrays.stream(clz.getDeclaredMethods()) + // .filter(m -> m + // .getName() + // .startsWith("set")) + // .collect(Collectors.toMap( + // Method::getName, + // Function.identity())); + // System.out.println(methodMap.keySet()); + + System.out.println("For class : " + clz.getSimpleName()); + T obj = clz.getDeclaredConstructor().newInstance(); + + for (Field jooqField : fields) { + String columnName = jooqField.getName(); + if (columnName.equals(primaryKeyCol)) { + continue; + } + + System.out.println("col name: " + columnName); + if (columnName.startsWith(NESTED_OBJECT_COLUMN_PREFIX)) { + String nestedTableName = columnName.replace(NESTED_OBJECT_COLUMN_PREFIX, ""); + if (jooqField.getType() == String.class) { + String value = (String) jooqField.getValue(record); + JsonArray array = JsonParser.parseString(value).getAsJsonArray(); + // CollectionReferenceType. + //Method setter = methodMap.get("set" + nestedTableName); + Method setter = fieldNameToGetterSetterMap.get(nestedTableName).setter; + ParameterizedType type = (ParameterizedType) setter.getGenericParameterTypes()[0]; + Type[] typeArgs = type.getActualTypeArguments(); + if (typeArgs.length != 1) { + throw new IllegalStateException(); + } + + Class collectionOfType = (Class) typeArgs[0]; + + List collection = new ArrayList<>(); + for (JsonElement element: array) { + JsonObject jsonObject = element.getAsJsonObject(); + String actualTableName = jsonObject.get("tableName").getAsString(); + + Class actualTableClass = tableNameToJavaClassMap.get(actualTableName); + if (actualTableClass == null) { + throw new IllegalStateException("The table name '" + actualTableName + "' does not exist in the table to class mapping. But" + + "the database row mentions it: " + element.toString()); + } + + for (JsonElement rowIdElem: jsonObject.get("rowIds").getAsJsonArray()) { + int rowIdNestedTable = rowIdElem.getAsInt(); + Object nestedObj = read(actualTableClass, rowIdNestedTable); + collection.add(nestedObj); + } + } + + setter.invoke(obj, collection); + } else if (jooqField.getType() == Integer.class) { + // ReferenceObjectType + //Method setter = methodMap.get("set"+nestedTableName); + if (fieldNameToGetterSetterMap.get(nestedTableName) == null) { + throw new IllegalStateException("No Field Mapping exist for column name " + jooqField.getName() + " of table " + tableName); + } + Method setter = fieldNameToGetterSetterMap.get(nestedTableName).setter; + Class setterType = setter.getParameterTypes()[0]; + + int nestedRowId = (int)jooqField.getValue(record); + Object nestedObj = read(setterType, nestedRowId); + setter.invoke(obj, nestedObj); + + } + else { + throw new IllegalStateException("ReferenceColumn can be either Integer or String."); + } + } else { + Class fieldType = jooqField.getType(); + // For all the other columns, we look for the corresponding setter. + String methodName = "set" + columnName; + Method setter = fieldNameToGetterSetterMap.get(jooqField.getName()).setter; + setter.invoke(obj, jooqField.getType().cast(jooqField.getValue(record))); + } + } + return obj; + } + + synchronized long writeImpl(T obj) + throws IllegalStateException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SQLException, + IllegalAccessException { + return determineColumnsAndTheirValuesFromObject(obj); + } + + private static String getTableNameFromClassName(Class clz) { + return clz.getSimpleName(); + } + + private Class getGenericParamTypeOfMethod(Method method) { + ParameterizedType mtype = (ParameterizedType) method.getGenericReturnType(); + Type[] mTypeArguments = mtype.getActualTypeArguments(); + if (mTypeArguments.length != 1) { + throw new IllegalStateException("Expected list of a single type. Please check method: " + method.getName()); + } + Class mTypeArgClass = (Class) mTypeArguments[0]; + return mTypeArgClass; + } + + private void checkPublic(Method method) { + if (!Modifier.isPublic(method.getModifiers())) { + throw new IllegalStateException("Found '" + method.getName() + "'. But it is not public."); + } + } + + private String capitalize(String name) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + + /** + * Go over all the fields of the class and then filter out all that are annotated as @AColumn or @ATable. For those fields, + * try to figure out the getter and setters. + * @param clz The class whose field registry is to be created. + * @param The Generic type of the class. + * @throws IllegalStateException When getters and setters are not found for the field that is required to be persisted or they exist but + * are not public. + */ + private void createFieldRegistry(Class clz) throws IllegalStateException { + fieldGetterSetterPairsMap.putIfAbsent(clz, new HashMap<>()); + classFieldNamesToGetterSetterMap.putIfAbsent(clz, new HashMap<>()); + + Map fieldToGetterSetterMap = fieldGetterSetterPairsMap.get(clz); + Map fieldNameToGetterSetterMap = classFieldNamesToGetterSetterMap.get(clz); + + for (java.lang.reflect.Field field : clz.getDeclaredFields()) { + if (field.isAnnotationPresent(AColumn.class) || field.isAnnotationPresent(ATable.class)) { + // Now we try to find the corresponding Getter and Setter for this field. + GetterSetterPairs pair = new GetterSetterPairs(); + + String capitalizedFieldName = capitalize(field.getName()); + for (String prefix: GETTER_PREFIXES) { + String key = prefix + capitalizedFieldName; + try { + Method method = clz.getDeclaredMethod(key); + if (method.getReturnType() != field.getType()) { + throw new IllegalStateException("The types of the getter '" + key + "' and field '" + field.getName() + "' don't match."); + } + checkPublic(method); + pair.getter = method; + break; + } catch (NoSuchMethodException e) { + } + } + for (String prefix: SETTER_PREFIXES) { + String key = prefix + capitalizedFieldName; + try { + Method method = clz.getDeclaredMethod(key, field.getType()); + if (method.getParameterTypes()[0] != field.getType()) { + throw new IllegalStateException("The types of the setter '" + key + "' and field '" + field.getName() + "' don't match."); + } + checkPublic(method); + pair.setter = method; + break; + } catch (NoSuchMethodException e) { + } + } + if (pair.getter == null || pair.setter == null) { + throw new IllegalStateException("Could not find getter or setter for the field '" + field.getName() + "' of class '" + + clz.getName() + + "'. Getters are expected to start with 'get' or 'is' and setters are expected to start with 'set' and they are required to" + + " end with the name of the field (case insensitive.)"); + } + fieldToGetterSetterMap.put(field, pair); + fieldNameToGetterSetterMap.put(field.getName(), pair); + } + } + } + + private ColumnValuePair writeCollectionReferenceColumn(java.lang.reflect.Field field, Method getter, T obj) + throws InvocationTargetException, IllegalAccessException, SQLException { + ColumnValuePair columnValuePair = new ColumnValuePair(); + String columnName = NESTED_OBJECT_COLUMN_PREFIX + field.getName(); + + Collection collection = (Collection) getter.getReturnType().cast(getter.invoke(obj)); + Map> nestedPrimaryKeys = new HashMap<>(); + for (Object o: collection) { + String myActualType = o.getClass().getSimpleName(); + nestedPrimaryKeys.putIfAbsent(myActualType, new ArrayList<>()); + + Class typeArgClass = getGenericParamTypeOfMethod(getter); + + int id = determineColumnsAndTheirValuesFromObject(typeArgClass.cast(o)); + nestedPrimaryKeys.get(myActualType).add(id); + } + JsonArray json = new JsonArray(); + // Create fields with the collectionReferenceType columns + for (Map.Entry> colNameEntry: nestedPrimaryKeys.entrySet()) { + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArrayInner = new JsonArray(); + colNameEntry.getValue().forEach(rowId -> jsonArrayInner.add(rowId)); + + jsonObject.addProperty("tableName", colNameEntry.getKey()); + jsonObject.add("rowIds", jsonArrayInner); + + json.add(jsonObject); + } + columnValuePair.field = (DSL.field(DSL.name(columnName), String.class)); + columnValuePair.value = json.toString(); + return columnValuePair; + } + + private int determineColumnsAndTheirValuesFromObject(T obj) throws IllegalStateException, IllegalAccessException, + InvocationTargetException, SQLException { + Class clz = obj.getClass(); + String tableName = getTableNameFromClassName(clz); + Table table = DSL.table(tableName); + + // If there exists a table with the same name as the SimpleName of this class, make sure that the persisted class is same as this. + // This is to avoid cases of trying to persist classes from two different packages with the same name. + if (jooqTableColumns.containsKey(tableName)) { + // There can be a case where two distinct classes with the same SimpleName, might want + // to persist themselves. In this case, we should keep things simple and throw an error. + Class alreadyStoredTableClass = tableNameToJavaClassMap.get(tableName); + Objects.requireNonNull(alreadyStoredTableClass, "A table exists with this name but the table is not mapped to a Java class."); + if (alreadyStoredTableClass != clz) { + throw new IllegalStateException("There is already a table in the Database with the same name. It belongs to the class: '" + + alreadyStoredTableClass + "'. Please consider re-naming your classes."); + } + Objects.requireNonNull(fieldGetterSetterPairsMap.get(clz), "Because the class is already persisted once, we should have the " + + "mapping for field to their corresponding getter and setters."); + } else { + createFieldRegistry(clz); + } + + Map fieldToGetterSetterMap = fieldGetterSetterPairsMap.get(clz); + List> fields = new ArrayList<>(); + List values = new ArrayList<>(); + + for (Map.Entry entry: fieldToGetterSetterMap.entrySet()) { + Method getter = entry.getValue().getter; + java.lang.reflect.Field classField = entry.getKey(); + + String columnName = classField.getName(); + Class retType = getter.getReturnType(); + + if (classField.isAnnotationPresent(ATable.class)) { + columnName = NESTED_OBJECT_COLUMN_PREFIX + columnName; + if (Collection.class.isAssignableFrom(retType)) { + ColumnValuePair columnValuePair = writeCollectionReferenceColumn(classField, getter, obj); + fields.add(columnValuePair.field); + values.add(columnValuePair.value); + } else { + // This is a user-defined class Type + int id = determineColumnsAndTheirValuesFromObject(retType.cast(getter.invoke(obj))); + // Although the ID is long, we are persisting it as string because if there are multiple rows in the child table, that refer to + // the parent table row, then, the parent table should have a list of them. IN which case the value stored in the column will be + // of the form: [id1, id2, ..]. + fields.add(DSL.field(DSL.name(columnName), Integer.class)); + values.add(id); + } + } else if (retType.isPrimitive()) { + fields.add(DSL.field(DSL.name(columnName), retType)); + values.add(getter.invoke(obj)); + } else if (retType == String.class) { + fields.add(DSL.field(DSL.name(columnName), String.class)); + values.add(getter.invoke(obj)); + } + } + + if (fields.size() == 0) { + throw new IllegalStateException("Class '" + obj.getClass() + + "' was asked to be persisted but there are no getters with @AColumn annotation."); + } + + // If table does not exist, try to create one. + if (!jooqTableColumns.containsKey(tableName)) { + createTable(tableName, fields); + tableNameToJavaClassMap.put(tableName, obj.getClass()); + } + + try { + int ret = create.insertInto(table).columns(fields).values(values).execute(); + } catch (Exception e) { + LOG.error(e); + throw new SQLException(e); + } + int lastRowId = -1; + String sqlQuery = "SELECT " + LAST_INSERT_ROWID; + + try { + lastRowId = create.fetch(sqlQuery).get(0).get(LAST_INSERT_ROWID, Integer.class); + } catch (Exception e) { + LOG.error("Failed to insert into the table {}", table, e); + throw new SQLException(e); + } + LOG.debug("most recently inserted primary key = {}", lastRowId); + return lastRowId; + } + // This reads all SQLite tables in the latest SQLite file and converts the read data to JSON. @Override synchronized String readTables() { diff --git a/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SqliteObjectPersistor.java b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SqliteObjectPersistor.java new file mode 100644 index 000000000..d0eb80b20 --- /dev/null +++ b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/SqliteObjectPersistor.java @@ -0,0 +1,198 @@ +package com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence; + +import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.pck1.TestPersist; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class SqliteObjectPersistor { + private Path testLocation = null; + private final String baseFilename = "rca.test.file"; + + @Before + public void init() throws IOException { + String cwd = System.getProperty("user.dir"); + testLocation = Paths.get(cwd, "src", "test", "resources", "tmp", "file_rotate"); + Files.createDirectories(testLocation); + FileUtils.cleanDirectory(testLocation.toFile()); + } + + @After + public void cleanup() throws IOException { + FileUtils.cleanDirectory(testLocation.toFile()); + } + + @Test + public void testWriteObject() throws Exception { + SQLitePersistor sqlite = new SQLitePersistor( + testLocation.toString(), baseFilename, String.valueOf(1), TimeUnit.SECONDS, 1); + Outer outer = new Outer(); + sqlite.write(outer); + System.out.println(sqlite.read()); + + Outer outerOut = sqlite.read(Outer.class); + + Assert.assertEquals(outer.x, outerOut.x); + Assert.assertEquals(outer.name, outerOut.name); + Assert.assertEquals(outer.bObj.x, outerOut.bObj.x, 0.01); + + System.out.println(outerOut); + } + + @Rule + public ExpectedException exceptionRule = ExpectedException.none(); + + @Test + public void persistTwoClassesWithSameName() throws Exception { + exceptionRule.expect(IllegalStateException.class); + exceptionRule.expectMessage("There is already a table in the Database with the same name"); + + TestPersist testPersist1 = new TestPersist(); + com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.pck2.TestPersist testPersist2 = + new com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.pck2.TestPersist(); + + SQLitePersistor sqlite = new SQLitePersistor( + testLocation.toString(), baseFilename, String.valueOf(1), TimeUnit.SECONDS, 1); + sqlite.write(testPersist1); + sqlite.write(testPersist2); + } + + static class Outer { + @AColumn + int x; + + int y; + + @AColumn + String name; + + @ATable + B bObj; + + @ATable + List myList; + + public Outer() { + this.x = 10; + this.y = 20; + this.name = "test-name"; + this.bObj = new B(); + this.myList = new ArrayList<>(); + myList.add(new ITestImpl1()); + myList.add(new ITestImpl1()); + myList.add(new ITestImpl2()); + } + + public void setY(Integer y) { + this.y = y; + } + + public void setBObj(B bObj) { + this.bObj = bObj; + } + + public void setMyList(List myList) { + this.myList = myList; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public B getBObj() { + return bObj; + } + + public List getMyList() { + return myList; + } + } + + static class B { + @AColumn + double x = 5.55; + int y = 7; + + public B() { + } + + public void setX(double x) { + this.x = x; + } + + public void setY(Integer y) { + this.y = y; + } + + public double getX() { + return x; + } + + public int getY() { + return y; + } + } + + interface ITest { + + } + + static class ITestImpl1 implements ITest { + @AColumn + boolean yes = true; + + public void setYes(boolean yes) { + this.yes = yes; + } + + public ITestImpl1() { + } + + public boolean isYes() { + return yes; + } + } + + static class ITestImpl2 implements ITest { + @AColumn + boolean no = false; + + public ITestImpl2() { + } + + public void setNo(boolean no) { + this.no = no; + } + + public boolean isNo() { + return no; + } + } +} diff --git a/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/pck1/TestPersist.java b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/pck1/TestPersist.java new file mode 100644 index 000000000..b73d77e3e --- /dev/null +++ b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/pck1/TestPersist.java @@ -0,0 +1,20 @@ +package com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.pck1; + +import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.AColumn; + +public class TestPersist { + @AColumn + int x; + + public TestPersist() { + this.x = 1; + } + + public int getX() { + return x; + } + + public void setX(int x) { + x = x; + } +} diff --git a/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/pck2/TestPersist.java b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/pck2/TestPersist.java new file mode 100644 index 000000000..0fa37cc2c --- /dev/null +++ b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/rca/persistence/pck2/TestPersist.java @@ -0,0 +1,16 @@ +package com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.pck2; + +import com.amazon.opendistro.elasticsearch.performanceanalyzer.rca.persistence.AColumn; + +public class TestPersist { + @AColumn + int x; + + public TestPersist() { + this.x = 2; + } + + public int getX() { + return x; + } +}