package com.netflix.astyanax.entitystore; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import org.apache.commons.lang.RandomStringUtils; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import com.google.common.collect.ImmutableMap; import com.netflix.astyanax.AstyanaxContext; import com.netflix.astyanax.Keyspace; import com.netflix.astyanax.connectionpool.NodeDiscoveryType; import com.netflix.astyanax.connectionpool.impl.ConnectionPoolConfigurationImpl; import com.netflix.astyanax.connectionpool.impl.ConnectionPoolType; import com.netflix.astyanax.connectionpool.impl.CountingConnectionPoolMonitor; import com.netflix.astyanax.impl.AstyanaxConfigurationImpl; import com.netflix.astyanax.model.ColumnFamily; import com.netflix.astyanax.model.ColumnList; import com.netflix.astyanax.serializers.StringSerializer; import com.netflix.astyanax.thrift.ThriftFamilyFactory; import com.netflix.astyanax.util.SingletonEmbeddedCassandra; public class DefaultEntityManagerTtlTest { private static Keyspace keyspace; private static AstyanaxContext<Keyspace> keyspaceContext; private static String TEST_CLUSTER_NAME = "junit_cass_sandbox"; private static String TEST_KEYSPACE_NAME = "EntityPersisterTestKeyspace"; private static final String SEEDS = "localhost:9160"; public static ColumnFamily<String, String> CF_SAMPLE_ENTITY = ColumnFamily.newColumnFamily( "SampleEntityColumnFamily", StringSerializer.get(), StringSerializer.get()); public static ColumnFamily<String, String> CF_SIMPLE_ENTITY = ColumnFamily.newColumnFamily( "SimpleEntityColumnFamily", StringSerializer.get(), StringSerializer.get()); @BeforeClass public static void setup() throws Exception { SingletonEmbeddedCassandra.getInstance(); Thread.sleep(1000 * 3); createKeyspace(); Thread.sleep(1000 * 3); } @AfterClass public static void teardown() throws Exception { if (keyspaceContext != null) keyspaceContext.shutdown(); Thread.sleep(1000 * 10); } private static void createKeyspace() throws Exception { keyspaceContext = new AstyanaxContext.Builder() .forCluster(TEST_CLUSTER_NAME) .forKeyspace(TEST_KEYSPACE_NAME) .withAstyanaxConfiguration( new AstyanaxConfigurationImpl() .setDiscoveryType(NodeDiscoveryType.RING_DESCRIBE) .setConnectionPoolType(ConnectionPoolType.ROUND_ROBIN)) .withConnectionPoolConfiguration( new ConnectionPoolConfigurationImpl(TEST_CLUSTER_NAME + "_" + TEST_KEYSPACE_NAME) .setSocketTimeout(30000) .setMaxTimeoutWhenExhausted(2000) .setMaxConnsPerHost(20) .setInitConnsPerHost(10) .setSeeds(SEEDS)) .withConnectionPoolMonitor(new CountingConnectionPoolMonitor()) .buildKeyspace(ThriftFamilyFactory.getInstance()); keyspaceContext.start(); keyspace = keyspaceContext.getEntity(); try { keyspace.dropKeyspace(); } catch (Exception e) { e.printStackTrace(); } keyspace.createKeyspace(ImmutableMap.<String, Object>builder() .put("strategy_options", ImmutableMap.<String, Object>builder() .put("replication_factor", "1") .build()) .put("strategy_class", "SimpleStrategy") .build() ); keyspace.createColumnFamily(CF_SAMPLE_ENTITY, null); keyspace.createColumnFamily(CF_SIMPLE_ENTITY, null); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Entity @TTL(2) private static class TtlEntity { @Id private String id; @Column private String column; public TtlEntity() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getColumn() { return column; } public void setColumn(String column) { this.column = column; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TtlEntity other = (TtlEntity) obj; if(id.equals(other.id) && column.equals(other.column)) return true; else return false; } @Override public String toString() { return "SimpleEntity [id=" + id + ", column=" + column + "]"; } } private TtlEntity createTtlEntity(String id) { TtlEntity e = new TtlEntity(); e.setId(id); e.setColumn(RandomStringUtils.randomAlphanumeric(4)); return e; } @Test public void testTtlClassAnnotation() throws Exception { final String id = "testTtlClassAnnotation"; EntityManager<TtlEntity, String> entityPersister = new DefaultEntityManager.Builder<TtlEntity, String>() .withEntityType(TtlEntity.class) .withKeyspace(keyspace) .withColumnFamily(CF_SAMPLE_ENTITY) .build(); TtlEntity origEntity = createTtlEntity(id); entityPersister.put(origEntity); // use low-level astyanax API to confirm the write { ColumnList<String> cl = keyspace.prepareQuery(CF_SAMPLE_ENTITY).getKey(id).execute().getResult(); // test column number Assert.assertEquals(1, cl.size()); // test column value Assert.assertEquals(origEntity.getColumn(), cl.getColumnByName("column").getStringValue()); // custom ttl Assert.assertEquals(2, cl.getColumnByName("column").getTtl()); } TtlEntity getEntity = entityPersister.get(id); Assert.assertEquals(id, getEntity.getId()); Assert.assertEquals(origEntity, getEntity); // entity should expire after 3s since TTL is 2s in annotation Thread.sleep(1000 * 3); // use low-level astyanax API to confirm the TTL expiration { ColumnList<String> cl = keyspace.prepareQuery(CF_SAMPLE_ENTITY).getKey(id).execute().getResult(); Assert.assertEquals(0, cl.size()); } } @Test public void testConstructorTtlOverride() throws Exception { final String id = "testConstructorTtlOverride"; EntityManager<TtlEntity, String> entityPersister = new DefaultEntityManager.Builder<TtlEntity, String>() .withEntityType(TtlEntity.class) .withKeyspace(keyspace) .withColumnFamily(CF_SAMPLE_ENTITY) .withTTL(5) .build(); TtlEntity origEntity = createTtlEntity(id); entityPersister.put(origEntity); // use low-level astyanax API to confirm the write { ColumnList<String> cl = keyspace.prepareQuery(CF_SAMPLE_ENTITY).getKey(id).execute().getResult(); // test column number Assert.assertEquals(1, cl.size()); // test column value Assert.assertEquals(origEntity.getColumn(), cl.getColumnByName("column").getStringValue()); // custom ttl Assert.assertEquals(5, cl.getColumnByName("column").getTtl()); } TtlEntity getEntity = entityPersister.get(id); Assert.assertEquals(origEntity, getEntity); // entity should still be alive after 3s since TTL is overriden to 5s Thread.sleep(1000 * 3); getEntity = entityPersister.get(id); Assert.assertEquals(origEntity, getEntity); // entity should expire after 3s since 6s have passed with 5s TTL Thread.sleep(1000 * 3); // use low-level astyanax API to confirm the TTL expiration { ColumnList<String> cl = keyspace.prepareQuery(CF_SAMPLE_ENTITY).getKey(id).execute().getResult(); Assert.assertEquals(0, cl.size()); } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Entity private static class MethodTtlEntity { @Id private String id; @Column private String column; public MethodTtlEntity() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getColumn() { return column; } public void setColumn(String column) { this.column = column; } @SuppressWarnings("unused") @TTL public Integer getTTL() { return 2; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MethodTtlEntity other = (MethodTtlEntity) obj; if(id.equals(other.id) && column.equals(other.column)) return true; else return false; } @Override public String toString() { return "MethodTtlEntity [id=" + id + ", column=" + column + "]"; } } private MethodTtlEntity createMethodTtlEntity(String id) { MethodTtlEntity e = new MethodTtlEntity(); e.setId(id); e.setColumn(RandomStringUtils.randomAlphanumeric(4)); return e; } @Test public void testMethodTtlOverride() throws Exception { final String id = "testMethodTtlOverride"; EntityManager<MethodTtlEntity, String> entityPersister = new DefaultEntityManager.Builder<MethodTtlEntity, String>() .withEntityType(MethodTtlEntity.class) .withKeyspace(keyspace) .withColumnFamily(CF_SAMPLE_ENTITY) .withTTL(60) // constructor TTL value is 60s .build(); MethodTtlEntity origEntity = createMethodTtlEntity(id); entityPersister.put(origEntity); // use low-level astyanax API to confirm the write { ColumnList<String> cl = keyspace.prepareQuery(CF_SAMPLE_ENTITY).getKey(id).execute().getResult(); // test column number Assert.assertEquals(1, cl.size()); // test column value Assert.assertEquals(origEntity.getColumn(), cl.getColumnByName("column").getStringValue()); // custom ttl Assert.assertEquals(2, cl.getColumnByName("column").getTtl()); } MethodTtlEntity getEntity = entityPersister.get(id); Assert.assertEquals(id, getEntity.getId()); Assert.assertEquals(origEntity, getEntity); // entity should still be alive after 4s since TTL is overridden to 2s Thread.sleep(1000 * 4); // use low-level astyanax API to confirm the TTL expiration { ColumnList<String> cl = keyspace.prepareQuery(CF_SAMPLE_ENTITY).getKey(id).execute().getResult(); Assert.assertEquals(0, cl.size()); } } }