/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * http://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package org.glassfish.jersey.internal.util; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.glassfish.jersey.internal.jsr166.Flow; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Test Jersey {@link Flow.Publisher} implementation, {@link JerseyPublisher}. * * @author Adam Lindenthal (adam.lindenthal at oracle.com) */ public class JerseyPublisherTest { @Test public void test() throws InterruptedException { final CountDownLatch openLatch1 = new CountDownLatch(1); final CountDownLatch openLatch2 = new CountDownLatch(1); final CountDownLatch openLatch3 = new CountDownLatch(1); final CountDownLatch writeLatch1 = new CountDownLatch(3); final CountDownLatch writeLatch2 = new CountDownLatch(2); final CountDownLatch writeLatch3 = new CountDownLatch(1); final CountDownLatch closeLatch = new CountDownLatch(3); final JerseyPublisher<String> publisher = new JerseyPublisher<>(JerseyPublisher.PublisherStrategy.BLOCKING); final PublisherTestSubscriber subscriber1 = new PublisherTestSubscriber("SUBSCRIBER-1", openLatch1, writeLatch1, closeLatch); final PublisherTestSubscriber subscriber2 = new PublisherTestSubscriber("SUBSCRIBER-2", openLatch2, writeLatch2, closeLatch); final PublisherTestSubscriber subscriber3 = new PublisherTestSubscriber("SUBSCRIBER-3", openLatch3, writeLatch3, closeLatch); publisher.publish("START"); // sent before any subscriber subscribed - should not be received publisher.subscribe(subscriber1); publisher.publish("Zero"); // before receive, but should be received by SUBSCRIBER-1 assertTrue(openLatch1.await(200, TimeUnit.MILLISECONDS)); subscriber1.receive(3); publisher.publish("One"); // should be received by SUBSCRIBER-1 publisher.subscribe(subscriber2); assertTrue(openLatch2.await(200, TimeUnit.MILLISECONDS)); subscriber2.receive(5); publisher.publish("Two"); // should be received by SUBSCRIBER-1 and SUBSCRIBER-2 publisher.subscribe(subscriber3); assertTrue(openLatch3.await(200, TimeUnit.MILLISECONDS)); subscriber3.receive(5); publisher.publish("Three"); // should be received by SUBSCRIBER-2 and SUBSCRIBER-3 assertTrue(writeLatch1.await(1000, TimeUnit.MILLISECONDS)); assertTrue(writeLatch2.await(1000, TimeUnit.MILLISECONDS)); assertTrue(writeLatch3.await(1000, TimeUnit.MILLISECONDS)); Queue<String> result = subscriber1.getReceivedData(); assertEquals(3, result.size()); assertEquals("Zero", result.poll()); assertEquals("One", result.poll()); assertEquals("Two", result.poll()); result = subscriber2.getReceivedData(); assertEquals(2, result.size()); assertEquals("Two", result.poll()); assertEquals("Three", result.poll()); result = subscriber3.getReceivedData(); assertEquals(1, result.size()); assertEquals("Three", result.poll()); publisher.close(); subscriber1.receive(1); // --> with this, the CDL is successfully counted down and await returns true assertTrue(closeLatch.await(10000, TimeUnit.MILLISECONDS)); // this behaviour is a little counter-intuitive, but confirmed as correct by Flow.SubmissionPublisher author, // Dough Lea on the JDK mailing list } @Test public void testNonBlocking() throws InterruptedException { final int MSG_COUNT = 300; final JerseyPublisher<String> publisher = new JerseyPublisher<>(); final CountDownLatch openLatchActive = new CountDownLatch(1); final CountDownLatch writeLatch = new CountDownLatch(MSG_COUNT); final CountDownLatch closeLatch = new CountDownLatch(1); final CountDownLatch openLatchDead = new CountDownLatch(1); final PublisherTestSubscriber deadSubscriber = new PublisherTestSubscriber("dead", openLatchDead, new CountDownLatch(0), new CountDownLatch(0)); final PublisherTestSubscriber activeSubscriber = new PublisherTestSubscriber("active", openLatchActive, writeLatch, closeLatch); // subscribe to publisher publisher.subscribe(deadSubscriber); assertTrue(openLatchDead.await(200, TimeUnit.MILLISECONDS)); publisher.subscribe(activeSubscriber); assertTrue(openLatchActive.await(200, TimeUnit.MILLISECONDS)); activeSubscriber.receive(1000); AtomicInteger counter = new AtomicInteger(0); final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleWithFixedDelay(() -> { int i = counter.getAndIncrement(); if (i >= MSG_COUNT) { scheduledExecutorService.shutdown(); return; } publisher.publish("MSG-" + i); }, 0, 10, TimeUnit.MILLISECONDS); assertTrue(writeLatch.await(6000, TimeUnit.MILLISECONDS)); assertEquals(MSG_COUNT, activeSubscriber.getReceivedData().size()); assertEquals(0, deadSubscriber.getReceivedData().size()); assertFalse(activeSubscriber.hasError()); assertTrue(deadSubscriber.hasError()); publisher.close(); assertTrue(closeLatch.await(6000, TimeUnit.MILLISECONDS)); assertTrue(activeSubscriber.isCompleted()); assertFalse(deadSubscriber.isCompleted()); } class PublisherTestSubscriber implements Flow.Subscriber<String> { private final String name; private final CountDownLatch openLatch; private final CountDownLatch writeLatch; private final CountDownLatch closeLatch; private Flow.Subscription subscription; private final Queue<String> data; private boolean hasError = false; private boolean completed = false; PublisherTestSubscriber(final String name, final CountDownLatch openLatch, final CountDownLatch writeLatch, final CountDownLatch closeLatch) { this.name = name; this.openLatch = openLatch; this.writeLatch = writeLatch; this.closeLatch = closeLatch; this.data = new ConcurrentLinkedQueue<>(); } @Override public void onSubscribe(final Flow.Subscription subscription) { this.subscription = subscription; openLatch.countDown(); } @Override public void onNext(final String item) { data.add(item); writeLatch.countDown(); } @Override public void onError(final Throwable throwable) { throwable.printStackTrace(); hasError = true; } @Override public void onComplete() { completed = true; closeLatch.countDown(); } @Override public String toString() { return this.name + " " + Thread.currentThread().getName(); } public void receive(final long n) { if (subscription != null) { subscription.request(n); } } /** * Retrieve stored (received) data for assertions. * * @return all the data received by subscriber. */ Queue<String> getReceivedData() { return this.data; } boolean hasError() { return hasError; } boolean isCompleted() { return completed; } } }