/**
* JBoss, Home of Professional Open Source
* Copyright Red Hat, Inc., and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.aerogear.unifiedpush.message.serviceHolder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.jms.Queue;
import org.jboss.aerogear.unifiedpush.message.serviceHolder.ApnsServiceHolder;
import org.jboss.aerogear.unifiedpush.message.serviceHolder.ServiceConstructor;
import org.jboss.aerogear.unifiedpush.message.serviceHolder.ServiceDestroyer;
import org.jboss.aerogear.unifiedpush.test.archive.UnifiedPushArchive;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(Arquillian.class)
public class TestAbstractServiceHolderOnSingleNode {
private static final int INSTANCE_LIMIT = 5;
private static final long INSTANTIATION_TIMEOUT = 200;
private static final String PUSH_MESSAGE_ID = UUID.randomUUID().toString();
private static final String VARIANT_ID = UUID.randomUUID().toString();
@Deployment
public static WebArchive archive() {
return UnifiedPushArchive.forTestClass(TestAbstractServiceHolderOnSingleNode.class)
.withMessaging()
.addPackage(org.jboss.aerogear.unifiedpush.message.serviceHolder.AbstractServiceHolder.class.getPackage())
.deleteClass(ApnsServiceHolder.class)
.addClasses(MockServiceHolderForSingleNode.class)
.withMockito()
.as(WebArchive.class);
}
private ServiceConstructor<Integer> mockConstructor;
private ServiceDestroyer<Integer> mockDestroyed;
private AtomicInteger instanceCounter = new AtomicInteger();
@Inject
private MockServiceHolderForSingleNode holder;
@Resource(mappedName = "java:/queue/FreeServiceSlotQueue")
private Queue queue;
@Before
public void setUp() {
holder.initialize(PUSH_MESSAGE_ID, VARIANT_ID);
mockConstructor = new ServiceConstructor<Integer>() {
@Override
public Integer construct() {
return instanceCounter.incrementAndGet();
}
};
mockDestroyed = new ServiceDestroyer<Integer>() {
@Override
public void destroy(Integer instance) {
}
};
}
@After
public void tearDown() {
if (holder != null) {
holder.destroy(PUSH_MESSAGE_ID, VARIANT_ID);
}
}
@Test
public void allows_to_free_up_slots() throws ExecutionException {
for (Integer i = 1; i <= INSTANCE_LIMIT - 1; i++) {
// create first service
assertNotNull(holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor));
// create second service
assertEquals(new Integer(i * 2), holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor));
// simulate that second service has died
holder.freeUpSlot(PUSH_MESSAGE_ID, VARIANT_ID);
}
for (Integer i = 1; i <= INSTANCE_LIMIT - 1; i++) {
// simulate that services died
holder.freeUpSlot(PUSH_MESSAGE_ID, VARIANT_ID);
}
for (Integer i = 1; i <= INSTANCE_LIMIT; i++) {
// since all previous services died, we can now create up to 5 services again
assertNotNull(holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor));
}
assertNull("No more services should be created", holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor));
}
@Test
public void allows_to_return_freed_up_services_to_queue() throws ExecutionException {
for (Integer i = 1; i <= INSTANCE_LIMIT - 1; i++) {
Integer service = holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor);
assertEquals(i, service);
holder.queueFreedUpService(PUSH_MESSAGE_ID, VARIANT_ID, service, mockDestroyed);
service = holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor);
assertEquals(i, service);
}
}
@Test
public void returns_null_when_no_slots_available() throws ExecutionException {
for (Integer i = 1; i <= INSTANCE_LIMIT; i++) {
assertEquals(i, holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor));
}
assertNull(holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor));
}
@Test
public void cache_is_blocking_until_service_is_available() throws InterruptedException {
final int threadCount = 4;
final CountDownLatch latch = new CountDownLatch(threadCount * INSTANCE_LIMIT);
for (int i = 0; i < threadCount ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < INSTANCE_LIMIT; i++) {
Integer service = holder.dequeueOrCreateNewService(PUSH_MESSAGE_ID, VARIANT_ID, mockConstructor);
sleep(INSTANTIATION_TIMEOUT / (threadCount * 2));
holder.queueFreedUpService(PUSH_MESSAGE_ID, VARIANT_ID, service, mockDestroyed);
if (service != null) {
latch.countDown();
}
sleep(INSTANTIATION_TIMEOUT / (threadCount * 2));
}
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}).start();
}
if (!latch.await(INSTANTIATION_TIMEOUT * threadCount * 2, TimeUnit.MILLISECONDS)) {
fail("All threads didnt finish");
}
}
@Test
public void allows_to_call_dequeue_when_no_instance_was_created() throws ExecutionException {
assertNull(holder.dequeue("non-existent", "non-existent"));
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}