├── .gitignore ├── LICENSE.md ├── README.md ├── pom.xml └── src └── test ├── java └── com │ └── martinandersson │ └── javaee │ ├── arquillian │ ├── clientserver │ │ ├── ClientServerTest.java │ │ ├── ServerAPI.java │ │ └── package-info.java │ ├── helloworld │ │ ├── HelloWorldEJB.java │ │ ├── HelloWorldTest.java │ │ └── package-info.java │ ├── package-info.java │ └── persistence │ │ ├── Address.java │ │ ├── PersistenceTest.java │ │ ├── Person.java │ │ ├── PersonRepository.java │ │ └── package-info.java │ ├── cdi │ ├── alternatives │ │ ├── AlternativeDriver.java │ │ ├── AlternativeTest.java │ │ ├── UnselectedAlternativeTest.java │ │ └── lib │ │ │ ├── DefaultUsernameService.java │ │ │ ├── PermissiveUsernameService.java │ │ │ ├── UnselectedUsernameService.java │ │ │ └── UsernameService.java │ ├── extensions │ │ └── package-info.java │ ├── lookup │ │ ├── AbstractId.java │ │ ├── ApplicationScopedBean.java │ │ ├── DependentBean.java │ │ ├── InstanceVersusProviderRunner.java │ │ ├── InstanceVersusProviderTest.java │ │ ├── ProgrammaticLookupRunner.java │ │ ├── ProgrammaticLookupTest.java │ │ ├── RequestScopedBean.java │ │ └── package-info.java │ ├── package-info.java │ ├── packaging │ │ ├── ExplicitPackageTest.java │ │ ├── ImplicitPackageInvalidTest.java │ │ ├── ImplicitPackageValidTest.java │ │ ├── lib │ │ │ ├── CalculatorManagedBean.java │ │ │ ├── CalculatorRequestScoped.java │ │ │ └── CalculatorUnAnnotated.java │ │ └── package-info.java │ ├── producers │ │ └── entitymanager │ │ │ ├── package-info.java │ │ │ ├── producer │ │ │ ├── DependentEMProducer.java │ │ │ ├── ProducerClassTest.java │ │ │ ├── RequestScopedEMProducer.java │ │ │ ├── Scope.java │ │ │ └── TestDriver.java │ │ │ └── unsafe │ │ │ ├── EMProducerExtension.java │ │ │ ├── EntityManagerConsumer.java │ │ │ ├── OracleProducer.java │ │ │ ├── OracleTest.java │ │ │ ├── OracleTestDriver.java │ │ │ └── UserDatabase.java │ ├── qualifiers │ │ ├── QualifierDriver.java │ │ ├── QualifierTest.java │ │ └── lib │ │ │ ├── Broccoli.java │ │ │ ├── Caloric.java │ │ │ ├── Healthy.java │ │ │ ├── Meat.java │ │ │ ├── Unhealthy.java │ │ │ └── Water.java │ ├── resolution │ │ ├── BeanTypeResolutionDriver.java │ │ ├── BeanTypeResolutionTest.java │ │ ├── GenericBeanUndeployableTest.java │ │ └── SimpleCalculator.java │ ├── scope │ │ ├── application │ │ │ ├── ApplicationScopedConcurrencyTest.java │ │ │ └── ConcurrentInvocationCounter.java │ │ ├── package-info.java │ │ └── request │ │ │ ├── ApplicationScopedBean.java │ │ │ ├── RequestScopedBean.java │ │ │ ├── RequestScopedTest.java │ │ │ ├── StatelessBean.java │ │ │ ├── TestDriver1.java │ │ │ └── TestDriver2.java │ ├── specializes │ │ ├── SpecializesDriver.java │ │ ├── SpecializesTest.java │ │ ├── UndeployableSpecializationTest.java │ │ └── lib │ │ │ ├── DefaultUserSettings.java │ │ │ ├── SmartCalculator.java │ │ │ ├── SpecializedUserSettings.java │ │ │ ├── StupidCalculator.java │ │ │ └── UserSettings.java │ └── transactional │ │ ├── CrashingBean.java │ │ └── ExceptionCauseTest.java │ ├── ejb │ ├── exceptions │ │ ├── CrashingBean1.java │ │ ├── CrashingBean2.java │ │ ├── CustomApplicationException.java │ │ ├── EJBTransactionRolledbackExceptionTest.java │ │ ├── RemoteCrashingBean.java │ │ ├── SystemAndApplicationExceptionTest.java │ │ └── package-info.java │ ├── inheritance │ │ ├── A.java │ │ ├── B.java │ │ ├── C.java │ │ ├── InheritanceAndSpecializesTest.java │ │ └── package-info.java │ ├── sessionbeans │ │ ├── AbstractSessionBean.java │ │ ├── SingletonBean.java │ │ ├── StatefulBean.java │ │ ├── StatelessBean.java │ │ ├── package-info.java │ │ ├── testdriver │ │ │ ├── EJBType.java │ │ │ ├── ExecutionSettings.java │ │ │ ├── Operation.java │ │ │ ├── Report.java │ │ │ └── TestDriver.java │ │ └── tests │ │ │ ├── AbstractSessionTest.java │ │ │ ├── SingletonTest.java │ │ │ ├── StatefulTest.java │ │ │ └── StatelessTest.java │ └── transactions │ │ ├── AnnotationInheritanceTest.java │ │ ├── Base1.java │ │ ├── Base2.java │ │ ├── Base3.java │ │ ├── Base4.java │ │ ├── Base5.java │ │ ├── Base7.java │ │ ├── Derived1.java │ │ ├── Derived2.java │ │ ├── Derived3.java │ │ ├── Derived4.java │ │ ├── Derived5.java │ │ ├── Derived6.java │ │ ├── Derived7.java │ │ ├── GenericFoo1.java │ │ ├── GenericFoo2.java │ │ ├── NonGenericFoo.java │ │ └── package-info.java │ ├── jpa │ ├── entitymanagers │ │ ├── AbstractJTAEntityManagerTest.java │ │ ├── EntityManagerExposer.java │ │ ├── applicationmanaged │ │ │ ├── JTATest.java │ │ │ └── ResourceLocalTest.java │ │ ├── containermanaged │ │ │ ├── AbstractContainerManagedEntityManagerTest.java │ │ │ ├── ExtendedTest.java │ │ │ └── TransactionScopedTest.java │ │ ├── lib │ │ │ ├── BeanManagedTx.java │ │ │ ├── ContainerManagedTx.java │ │ │ ├── Product.java │ │ │ ├── Products.java │ │ │ ├── SingletonWithExtended.java │ │ │ ├── StatefulWithExtended1.java │ │ │ ├── StatefulWithExtended2.java │ │ │ ├── StatefulWithExtended3.java │ │ │ ├── StatelessWithExtended.java │ │ │ ├── TransactionalWithExtended.java │ │ │ └── UnsynchronizedEM.java │ │ ├── misc │ │ │ ├── QueryingForEntitiesTest.java │ │ │ └── SynchronizationTest.java │ │ └── package-info.java │ └── mapping │ │ ├── elementcollection │ │ ├── ElementCollectionDefaultMappingTest.java │ │ ├── ElementCollectionSeparateTableTest.java │ │ ├── lib │ │ │ ├── Person.java │ │ │ ├── Repository.java │ │ │ └── Song.java │ │ └── package-info.java │ │ └── orphanremoval │ │ ├── AbstractId.java │ │ ├── CascadeNone.java │ │ ├── CascadeRemove.java │ │ ├── OrphanRemoval.java │ │ ├── OrphanRemovalTest.java │ │ ├── Owner.java │ │ └── Repository.java │ ├── jta │ └── listeners │ │ ├── package-info.java │ │ └── synchronizationregistry │ │ ├── ManagedBean.java │ │ ├── SynchronizationRegistryTest.java │ │ ├── TestDriver.java │ │ └── TransactionalManagedBean.java │ ├── package-info.java │ ├── resources │ ├── ArquillianDS.java │ ├── SchemaGenerationStrategy.java │ └── package-info.java │ └── utils │ ├── DeploymentBuilder.java │ ├── Deployments.java │ ├── HttpRequests.java │ ├── Lookup.java │ ├── PhasedExecutorService.java │ └── ThreadScheduler.java └── resources ├── arquillian.xml ├── ejb-jar.xml ├── persistence-dropcreate.xml ├── persistence-update.xml └── web.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.class 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | 15 | # Maven build directory 16 | target/ 17 | 18 | # NetBeans configuration file 19 | nb-configuration.xml 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Java EE Concepts 2 | --- 3 | 4 | A Java Maven project using Arqullian to explore Java EE concepts. 5 | 6 | The goal is to provide a smallish framework for exploration and testing of Java EE code without having to dig through piles of specifications. 7 | 8 | This project, as was the creator's original intent, suits well for Java EE education making "live coding" a plausible alternative for the teacher. 9 | 10 | Each package illustrate a Java EE concept and provide elaborative kickass comments that explain everything of relevance with quotes and references to Java EE specifications and other litterature. The test classes will "prove" what really happens on the inside of a Java EE application server. These tests execute without any hassle right in your IDE. 11 | 12 | Next stop, read: 13 | [./test/java/com/martinandersson/javaee/arquillian/package-info.java](https://github.com/MartinanderssonDotcom/java-ee-concepts/blob/master/src/test/java/com/martinandersson/javaee/arquillian/package-info.java) 14 | 15 | Required of you 16 | --- 17 | * JDK 8 18 | * Latest and greatest versions of GlassFish and/or WildFly running (profiles included for both) 19 | 20 | Failing tests? 21 | --- 22 | Not all tests are passed by GlassFish and WildFly. One could argue that is by design. The test code in this project work hard to demonstrate and explore technologies outlined in related Java EE specifications. The test code does not cater to limitations of Java EE product providers. If a test fail, go to the source code file. Read JavaDoc and other source code comments. Chances are high that the issue you ran into has been thoroughly described and reported as a bug somewhere. 23 | 24 | Contributors 25 | --- 26 | Martin Andersson (webmaster@martinandersson.com) 27 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/clientserver/ServerAPI.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.arquillian.clientserver; 2 | 3 | import com.martinandersson.javaee.arquillian.helloworld.HelloWorldEJB; 4 | import java.io.IOException; 5 | import java.util.concurrent.atomic.LongAdder; 6 | import javax.ejb.EJB; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.annotation.WebServlet; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | /** 14 | * A client accessible server interface, kind of.
15 | * 16 | * The Servlet accept only GET requests with a "hello" parameter whose value 17 | * will be returned in upper case.
18 | * 19 | * There exists a non-academic tradition of defining a remote interface to 20 | * automatically be a "facade" just because it is "remote". However, a remote 21 | * interface is a remote interface. The interface is in no way required to be a 22 | * facade for something else. A facade is by definition an aggregation of other 23 | * interfaces/API:s into one simplified interface; a higher level abstraction if 24 | * you will. A facade may or may not be remote.
25 | * 26 | * This class has nothing to do with EJB remote interfaces, nor is it a facade. 27 | * I wouldn't actually call this class anything. At worst, one could describe 28 | * the {@code ServerAPI} as a HTTP-based adapter for the {@code HelloWorldEJB} 29 | * bean. However, name calling in this context serves little purpose and should 30 | * be regarded as blasphemi. 31 | * 32 | * @author Martin Andersson (webmaster at martinandersson.com) 33 | */ 34 | @WebServlet(urlPatterns = "/ServerAPI") 35 | public class ServerAPI extends HttpServlet 36 | { 37 | /* 38 | * Only one ServerAPI Servlet exist in the server and it is not thread-safe. 39 | * Therefore we use LongAdder which is a concurrent and lock-free counter (note 1). 40 | */ 41 | 42 | private static final LongAdder requestCounter = new LongAdder(); 43 | 44 | public static long getRequestCount() { 45 | return requestCounter.sum(); 46 | } 47 | 48 | 49 | /* 50 | * All calls to EJB:s are serialized, the only exception is @Singleton with 51 | * bean managed concurrency, or @Singleton with container managed 52 | * concurrency and @Lock(LockType.READ). See EJB 3.2 specification, section 53 | * 3.4.9. 54 | */ 55 | 56 | @EJB 57 | HelloWorldEJB helloWorldEJB; 58 | 59 | @Override 60 | protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 61 | requestCounter.increment(); 62 | super.service(req, resp); 63 | } 64 | 65 | @Override 66 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 67 | final String world = req.getParameter("hello"); 68 | 69 | if (world == null) { 70 | reply("Missing the \"hello\" parameter :'(", HttpServletResponse.SC_BAD_REQUEST, resp); 71 | } 72 | else { 73 | String WORLD = helloWorldEJB.toUpperCase(world); 74 | reply(WORLD, HttpServletResponse.SC_OK, resp); 75 | } 76 | } 77 | 78 | private void reply(String message, int status, HttpServletResponse resp) throws IOException { 79 | /* 80 | * If SC_BAD_REQUEST, one would perhaps like to use sendError() instead 81 | * of setStatus(). But that would allow the server to also output a 82 | * vendor-specific HTML error page (!). 83 | */ 84 | 85 | resp.setStatus(status); 86 | 87 | resp.setContentType("text/plain"); // or "text/html;charset=UTF-8" and skip next statement 88 | resp.setCharacterEncoding("UTF-8"); 89 | resp.setContentLength(message.length()); // if the Servlet do buffering, which most do, they can infer the output length without this call 90 | 91 | resp.getWriter().write(message); 92 | } 93 | } 94 | 95 | 96 | 97 | /* 98 | * NOTE 1: LongAdder was added in JDK 8. Earlier JDK:s must find a sharded 99 | * counter from a library or write their own. In worst case, use 100 | * AtomicLong. 101 | */ -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/clientserver/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrates using Arquillian to manage two JVM:s, one running as client and 3 | * one running as server. This is as "real" as a test can become.
4 | * 5 | * Highlights: 6 | * 7 | *
12 | * 13 | * This EJB has no reason being an EJB which is an "expensive" component to use. 14 | * Instead, it would be better suited as a @Dependent CDI bean: meaning no 15 | * annotations at all (note 1), or as a utility class with static methods only. 16 | * However, we're using a heavy-duty EJB here only as proof of concept. 17 | * 18 | * @author Martin Andersson (webmaster at martinandersson.com) 19 | */ 20 | @Stateless 21 | @TransactionAttribute(TransactionAttributeType.SUPPORTS) // <-- note 2 22 | public class HelloWorldEJB 23 | { 24 | private final static Logger LOGGER = Logger.getLogger(HelloWorldEJB.class.getName()); 25 | 26 | 27 | /** 28 | * Will uppercase a provided String. 29 | * 30 | * @param what the thing to uppercase 31 | * 32 | * @return {@code what.toUpperCase()} 33 | */ 34 | public String toUpperCase(String what) { 35 | /* 36 | * This is a contrived example. Serious code, for example making a 37 | * username lower- or uppercased before transmission to a database, 38 | * should use toUpperCase(Locale.ROOT). 39 | */ 40 | 41 | return what.toUpperCase(); 42 | } 43 | 44 | 45 | /* 46 | * Usually, Java EE annotations apply only to business methods. A business 47 | * method of an EJB must have public access. However, life cycle annotations 48 | * like the ones below is an exception to the rule. 49 | */ 50 | 51 | @PostConstruct 52 | private void logConstruction() { 53 | LOGGER.info(() -> 54 | HelloWorldEJB.class.getSimpleName() + ": Instance was just constructed. My hash: " + this.hashCode() + "."); 55 | } 56 | 57 | @PreDestroy 58 | private void logDesctruction() { 59 | LOGGER.info(() -> 60 | HelloWorldEJB.class.getSimpleName() + ": Instance will be killed. My hash: " + this.hashCode() + "."); 61 | } 62 | } 63 | 64 | /* 65 | * NOTE 1: Go ahead and try remove the annotations on this bean. There's one 66 | * more thing you have to do before the test executes as "normal" 67 | * without the provided annotations. Add this line to the @Deployment 68 | * annotated method in HelloWorldTest.java: 69 | * 70 | * jar.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); 71 | * 72 | * What this line do is to add an empty 0-byte file in the META-INF 73 | * directory. Read more here: 74 | * 75 | * http://docs.oracle.com/javaee/7/tutorial/doc/cdi-adv001.htm 76 | * 77 | * NOTE 2: If this annotation was left out, it would default to: 78 | * "@TransactionAttribute(TransactionAttributeType.REQUIRED)". This file 79 | * has no purpose of explaining transactions. We do talk about that a 80 | * bit in the "persistence"-package. 81 | */ -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/helloworld/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The most simplest Arquillian setup you can think of. 3 | * 4 | * Highlights: 5 | * 6 | *
3 | * 4 | * Subpackages goes into depth of Arquillian and at the same time, introduce 5 | * core Java EE concepts such as servlets, EJB:s and JPA.
6 | * 7 | * Begin your study in the "helloworld"-subpackage, then move to the more 8 | * advanced packages "clientserver" and "persistence". Always read the 9 | * "package-info.java" file first.
10 | * 11 | * Packages that illustrate and test specific Java EE concepts will be placed 12 | * in: 13 | *
{@code 14 | * com.martinandersson.javaee.**concept** 15 | * }16 | */ 17 | package com.martinandersson.javaee.arquillian; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/persistence/Address.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.arquillian.persistence; 2 | 3 | import javax.persistence.Embeddable; 4 | 5 | /** 6 | * World's most simplest address. 7 | * 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | @Embeddable 11 | public class Address 12 | { 13 | private String street, city; 14 | 15 | 16 | 17 | public String getStreet() { 18 | return street; 19 | } 20 | 21 | public String getCity() { 22 | return city; 23 | } 24 | 25 | public Address setStreet(String street) { 26 | this.street = street; 27 | return this; 28 | } 29 | 30 | public Address setCity(String city) { 31 | this.city = city; 32 | return this; 33 | } 34 | 35 | 36 | 37 | /* 38 | * ------------------ 39 | * | OBJECT OVERRIDES | 40 | * ------------------ 41 | */ 42 | 43 | @Override 44 | public String toString() { 45 | return new StringBuilder(Address.class.getSimpleName()) 46 | .append("[") 47 | .append("street=").append(street) 48 | .append(", city=").append(city) 49 | .append("]") 50 | .toString(); 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/persistence/Person.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.arquillian.persistence; 2 | 3 | import java.util.Objects; 4 | import javax.persistence.Embedded; 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.Id; 8 | import javax.persistence.Table; 9 | import javax.persistence.Version; 10 | 11 | /** 12 | * World's most simplest JPA @Entity class.
13 | * 14 | * Current persistence providers (and the byte code tooling they use) are having 15 | * severe problems parsing Java 1.8 syntax. This is a problem with EclipseLink 16 | * 2.5.2 and Hibernate 4.3.5. The JDK 8 library can still be used in JPA 17 | * entities. Java 1.8 syntax and latest GlassFish/WildFly servers have no 18 | * problem with Java 1.8 syntax in application components such as EJB:s (GF 19 | * 4.0.0 break).
20 | * 21 | * Note that this person will use a table in a specific schema called 22 | * "ARQUILLIAN_PERSISTENCE". 23 | * 24 | * @author Martin Andersson (webmaster at martinandersson.com) 25 | */ 26 | @Entity 27 | @Table(schema = "ARQUILLIAN_PERSISTENCE") 28 | public class Person // note 1 29 | { 30 | @Id 31 | @GeneratedValue 32 | private long id; 33 | 34 | @Version 35 | private int modCount; 36 | 37 | // Is implicitly: @Basic 38 | private String name; 39 | 40 | @Embedded 41 | private Address address; 42 | 43 | 44 | 45 | /** 46 | * No-arg constructor required by JPA. Application code should have no 47 | * business using this constructor. 48 | */ 49 | protected Person() { 50 | // Empty 51 | } 52 | 53 | public Person(String name) { 54 | this.name = Objects.requireNonNull(name, "name is null"); 55 | } 56 | 57 | public long getId() { 58 | return id; 59 | } 60 | 61 | public String getName() { 62 | return name; 63 | } 64 | 65 | public Address getAddress() { 66 | return address; 67 | } 68 | 69 | public Person setAddress(Address address) { 70 | this.address = address; 71 | return this; // <-- for chaining calls 72 | } 73 | 74 | 75 | 76 | /* 77 | * ------------------ 78 | * | OBJECT OVERRIDES | 79 | * ------------------ 80 | */ 81 | 82 | @Override 83 | public String toString() { 84 | return new StringBuilder(Person.class.getSimpleName()) 85 | .append("[") 86 | .append("id=").append(id) 87 | .append(", name=").append(name) 88 | .append(", address=").append(address) 89 | .append("]") 90 | .toString(); 91 | } 92 | } 93 | 94 | /* 95 | * NOTE 1: It is always a good idea to make the entity implement Serializable. 96 | * However, it is not required until the day comes when the entity needs 97 | * to be serialized. JPA 2.1 specification, section 2.1 ("The Entity 98 | * Class"): 99 | * 100 | * "If an entity instance is to be passed by value as a detached 101 | * object (e.g., through a remote interface), the entity class must 102 | * implement the Serializable interface." 103 | */ -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/persistence/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JPA persistence using a real server with a real database. 3 | * 4 | * Highlights: 5 | * 6 | *
22 | * 23 | * An alternative bean must be selected in order to be picked up and used. 24 | * Otherwise, the alternative is ignored.
25 | * 26 | * CDI 1.1, section "2.7 Alternatives": 27 | *
{@code 28 | * 29 | * An alternative is a bean that must be explicitly selected if it should be 30 | * available for lookup, injection or EL resolution. 31 | * 32 | * }33 | * 34 | * Prior to CDI 1.1, alternatives were required to be "selected" in the 35 | * {@code beans.xml} file and such a selection is valid only for the bean 36 | * archive that the {@code beans.xml} file is put in. CDI 1.1 accept the 37 | * {@code @Priority} annotation as another way of selecting an 38 | * alternative1.
39 | * 40 | * Also see section "4.3 Specialization".
41 | * 42 | * 43 | * 44 | *
{@code 21 | * 22 | * An alternative is a bean that must be explicitly selected if it should be 23 | * available for lookup, injection or EL resolution. 24 | * 25 | * }26 | * 27 | * Thus, an alternative bean might be deployed to an archive but still not 28 | * eligible as an injection target unless it is also selected. See section 29 | * "5.1.1 Declaring selected alternatives". 30 | * 31 | * @author Martin Andersson (webmaster at martinandersson.com) 32 | */ 33 | @RunWith(Arquillian.class) 34 | public class UnselectedAlternativeTest 35 | { 36 | @Deployment 37 | public static WebArchive buildDeployment() { 38 | return new DeploymentBuilder(UnselectedAlternativeTest.class) 39 | .addEmptyBeansXMLFile() 40 | .add(AlternativeDriver.class, 41 | UsernameService.class, 42 | DefaultUsernameService.class, 43 | UnselectedUsernameService.class) 44 | .build(); 45 | } 46 | 47 | @Test 48 | @RunAsClient 49 | public void unselectedAlternativeIsDeployableButNotUsed(@ArquillianResource URL url) { 50 | AlternativeDriver.Report> report = HttpRequests.getObject(url); 51 | 52 | assertEquals("Expected that the default username service would be used. The alternative has not been selected!", 53 | DefaultUsernameService.class, report.beanType); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/lib/DefaultUsernameService.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives.lib; 2 | 3 | /** 4 | * A {@code @Default} username service. 5 | * 6 | * @author Martin Andersson (webmaster at martinandersson.com) 7 | */ 8 | // Is implicitly: @javax.enterprise.inject.Default 9 | public class DefaultUsernameService implements UsernameService 10 | { 11 | /** 12 | * {@inheritDoc} 13 | */ 14 | @Override 15 | public boolean isReserved(String username) { 16 | return !"admin".equalsIgnoreCase(username); 17 | } 18 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/lib/PermissiveUsernameService.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives.lib; 2 | 3 | import javax.annotation.Priority; 4 | import javax.enterprise.inject.Alternative; 5 | 6 | /** 7 | * An {@code @Alternative UsernameService} with {@code @Priority 1}.
8 | * 9 | * This service permit all usernames. Good to have as an alternative! For 10 | * example if we want to run some awesome cool tests that need to replace the 11 | * default username service. Given that this bean is selected (annotated with 12 | * {@code @Priority}), the bean should only be packaged and deployed if you 13 | * actually want this alternative to be used.
14 | *
15 | * Another way of selecting, and thus enabling, an @Alternative is to add his
16 | * class as an entry to the {@code
14 | *
15 | * A reserved username should not be accepted when creating new users.
16 | *
17 | * @param username the username
18 | * @return {@code true} if username is reserved, otherwise {@code false}
19 | */
20 | boolean isReserved(String username);
21 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/extensions/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * CDI extensions are currently not tested or described.
3 | *
4 | * However, you do have access to a CDI extension
5 | * {@linkplain com.martinandersson.javaee.cdi.producers.entitymanager.unsafe.EMProducerExtension here}
6 | * and it is programmatically setup in an archive using the API of
7 | * {@linkplain com.martinandersson.javaee.utils.Deployments Deployments}.
8 | *
9 | * You can read more about CDI extensions in chapter 11 of the CDI 1.2
10 | * specification.
11 | */
12 | package com.martinandersson.javaee.cdi.extensions;
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/AbstractId.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import java.util.concurrent.atomic.AtomicLong;
4 |
5 | public abstract class AbstractId
6 | {
7 | private static final AtomicLong SEQ = new AtomicLong();
8 |
9 | private final long id;
10 |
11 | public AbstractId() {
12 | id = SEQ.incrementAndGet();
13 | }
14 |
15 | public long getId() {
16 | return id;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/ApplicationScopedBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import javax.enterprise.context.ApplicationScoped;
4 |
5 | /**
6 | * TODO: JavaDoc
7 | *
8 | * @author Martin Andersson (webmaster at martinandersson.com)
9 | */
10 | @ApplicationScoped
11 | public class ApplicationScopedBean extends AbstractId {
12 | // Empty
13 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/DependentBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import javax.enterprise.context.Dependent;
4 |
5 | @Dependent
6 | public class DependentBean extends AbstractId {
7 | // Empty
8 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/InstanceVersusProviderRunner.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import java.io.IOException;
4 | import java.io.ObjectOutputStream;
5 | import javax.enterprise.inject.Instance;
6 | import javax.inject.Inject;
7 | import javax.inject.Provider;
8 | import javax.servlet.ServletException;
9 | import javax.servlet.annotation.WebServlet;
10 | import javax.servlet.http.HttpServlet;
11 | import javax.servlet.http.HttpServletRequest;
12 | import javax.servlet.http.HttpServletResponse;
13 |
14 | /**
15 | *
16 | * @author Martin Andersson (webmaster at martinandersson.com)
17 | */
18 | @WebServlet("")
19 | public class InstanceVersusProviderRunner extends HttpServlet
20 | {
21 | @Inject
22 | Instance
23 | *
24 | * CDI 1.2, section "5.6.1. The Instance interface":
25 | *
22 | *
23 | * Also see:
24 | *
19 | *
20 | * This is the most used approach and is "backward compatible" with CDI 1.0
21 | * (Java EE 6).
22 | *
23 | * See JavaDoc of {@linkplain ImplicitPackageValidTest}. GlassFish pass this
24 | * test, but remember that it is an "explicit bean archive" that is deployed.
25 | * Therefore, it is no surprise that injection of {@code @ManagedBean} work for
26 | * this archive even for GlassFish.
27 | *
28 | * @author Martin Andersson (webmaster at martinandersson.com)
29 | */
30 | @RunWith(Arquillian.class)
31 | public class ExplicitPackageTest
32 | {
33 | @Deployment
34 | public static WebArchive buildDeployment() {
35 | return new DeploymentBuilder(ExplicitPackageTest.class)
36 | .addEmptyBeansXMLFile()
37 | .add(CalculatorUnAnnotated.class,
38 | CalculatorManagedBean.class,
39 | CalculatorRequestScoped.class)
40 | .build();
41 | }
42 |
43 |
44 |
45 | // In this example. All injected beans here are "managed". Read more:
46 | // ./com/martinandersson/javaee/cdi/package-info.java
47 |
48 | @Inject
49 | CalculatorUnAnnotated unAnnotated;
50 |
51 | @Inject
52 | CalculatorManagedBean usingManagedAnnotation;
53 |
54 | @Inject
55 | CalculatorRequestScoped usingScopeAnnotation;
56 |
57 |
58 |
59 | @Test
60 | public void canInjectUnAnnotatedCalculator() {
61 | assertEquals(2, unAnnotated.sum(1, 1));
62 | }
63 |
64 | @Test
65 | public void canInjectBeanAnnotatedCalculator() {
66 | assertEquals(2, usingManagedAnnotation.sum(1, 1));
67 | }
68 |
69 | @Test
70 | public void canInjectScopeAnnotatedCalculator() {
71 | assertEquals(2, usingScopeAnnotation.sum(1, 1));
72 | }
73 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/packaging/ImplicitPackageValidTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.packaging;
2 |
3 | import com.martinandersson.javaee.cdi.packaging.lib.CalculatorRequestScoped;
4 | import com.martinandersson.javaee.utils.DeploymentBuilder;
5 | import javax.inject.Inject;
6 | import org.jboss.arquillian.container.test.api.Deployment;
7 | import org.jboss.arquillian.junit.Arquillian;
8 | import org.jboss.shrinkwrap.api.spec.WebArchive;
9 | import static org.junit.Assert.assertEquals;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 |
13 | /**
14 | * This test will not package a {@code beans.xml} descriptor file in the
15 | * deployment archive, meaning that the archive is an "implicit bean archive" in
16 | * which only annotated beans are eligible for CDI management.
17 | *
18 | *
19 | *
20 | *
29 | *
30 | *
31 | *
32 | *
41 | *
42 | * TODO: File a bug.
43 | *
44 | *
45 | *
46 | * @author Martin Andersson (webmaster at martinandersson.com)
47 | */
48 | @RunWith(Arquillian.class)
49 | public class ImplicitPackageValidTest
50 | {
51 | @Deployment
52 | public static WebArchive buildDeployment() {
53 | return new DeploymentBuilder(ImplicitPackageValidTest.class)
54 | .add(CalculatorRequestScoped.class)
55 | .build();
56 |
57 | // Note: adding CDI inspector causes GF 4.1 to fail the test.
58 | }
59 |
60 | @Inject
61 | CalculatorRequestScoped requestScoped;
62 |
63 | @Test
64 | public void canInjectAnnotatedCalculator() {
65 | assertEquals(2, requestScoped.sum(1, 1));
66 | }
67 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/packaging/lib/CalculatorManagedBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.packaging.lib;
2 |
3 | import java.util.stream.IntStream;
4 | import javax.annotation.ManagedBean;
5 |
6 | /**
7 | * World's most simplest calculator, supports one function: summation of
8 | * values.
9 | *
10 | * This class has one provided annotation: {@linkplain
11 | * javax.annotation.ManagedBean @javax.annotation.ManagedBean}.
12 | *
13 | * @author Martin Andersson (webmaster at martinandersson.com)
14 | */
15 | @ManagedBean
16 | public class CalculatorManagedBean {
17 | public long sum(int... values) {
18 | return IntStream.of(values).sum();
19 | }
20 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/packaging/lib/CalculatorRequestScoped.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.packaging.lib;
2 |
3 | import java.util.stream.IntStream;
4 | import javax.enterprise.context.RequestScoped;
5 |
6 | /**
7 | * World's most simplest calculator, supports one function: summation of
8 | * values.
9 | *
10 | * This class has one provided annotation: {@linkplain
11 | * javax.enterprise.context.RequestScoped @javax.enterprise.context.RequestScoped}.
12 | *
13 | * @author Martin Andersson (webmaster at martinandersson.com)
14 | */
15 | @RequestScoped
16 | public class CalculatorRequestScoped {
17 | public long sum(int... values) {
18 | return IntStream.of(values).sum();
19 | }
20 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/packaging/lib/CalculatorUnAnnotated.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.packaging.lib;
2 |
3 | import java.util.stream.IntStream;
4 |
5 | /**
6 | * World's most simplest calculator, supports one function: summation of
7 | * values.
8 | *
9 | * This class uses no annotations.
10 | *
11 | * @author Martin Andersson (webmaster at martinandersson.com)
12 | */
13 | public class CalculatorUnAnnotated {
14 | public long sum(int... values) {
15 | return IntStream.of(values).sum();
16 | }
17 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/producer/DependentEMProducer.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.producers.entitymanager.producer;
2 |
3 | import static com.martinandersson.javaee.cdi.producers.entitymanager.producer.Scope.Type.DEPENDENT;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.concurrent.atomic.AtomicInteger;
7 | import javax.annotation.PostConstruct;
8 | import javax.enterprise.context.Dependent;
9 | import javax.enterprise.context.RequestScoped;
10 | import javax.enterprise.inject.Disposes;
11 | import javax.enterprise.inject.Produces;
12 | import javax.persistence.EntityManager;
13 | import javax.persistence.EntityManagerFactory;
14 | import javax.persistence.PersistenceUnit;
15 |
16 | /**
17 | *
18 | * @author Martin Andersson (webmaster at martinandersson.com)
19 | */
20 | @Dependent
21 | public class DependentEMProducer
22 | {
23 | public static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();
24 |
25 | public static final List
22 | *
23 | * So why not just use the implementation type then, for example {@code @Inject
24 | * Broccoli broccoli}? In software development, we always prefer interface-based
25 | * programming. The code in this servlet do not depend on a specific
26 | * implementation. The code is implementation agnostic. What our code do is to
27 | * declare a semantic dependency of "healthy" and "unhealthy" foods. This
28 | * annotation may be used in many different places all over the software system.
29 | *
30 | * @author Martin Andersson (webmaster at martinandersson.com)
31 | */
32 | @WebServlet("")
33 | public class QualifierDriver extends HttpServlet
34 | {
35 | @Inject
36 | // This injection point is implicitly also annotated: @javax.enterprise.inject.Default
37 | Caloric water;
38 |
39 | @Inject @Healthy
40 | Caloric broccoli;
41 |
42 | @Inject @Unhealthy
43 | Caloric meat;
44 |
45 |
46 |
47 | @Override
48 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
49 | Report
5 | *
6 | * This class is annotated with {@code @Healthy}.
7 | *
8 | * @author Martin Andersson (webmaster at martinandersson.com)
9 | */
10 | @Healthy
11 | public class Broccoli implements Caloric
12 | {
13 | /**
14 | * A broccoli has exactly 2 calories.
15 | *
16 | * @return {@code 2}
17 | */
18 | @Override
19 | public int getCalories() {
20 | return 2;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/qualifiers/lib/Caloric.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.qualifiers.lib;
2 |
3 | /**
4 | * A {@code Caloric} class is a unit of container for any number of calories,
5 | * including negative values or zero.
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | public interface Caloric
10 | {
11 | /**
12 | * Returns the number of calories this {@code Caloric} entity stores.
13 | *
14 | * @return the number of calories this {@code Caloric} entity stores
15 | */
16 | default int getCalories() { return 0; };
17 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/qualifiers/lib/Healthy.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.qualifiers.lib;
2 |
3 | import static java.lang.annotation.ElementType.*;
4 | import java.lang.annotation.Retention;
5 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
6 | import java.lang.annotation.Target;
7 | import javax.inject.Qualifier;
8 |
9 | /**
10 | * All healthy food are extinguished using this qualifier.
11 | *
12 | * @author Martin Andersson (webmaster at martinandersson.com)
13 | */
14 | @Qualifier // <-- Note that a qualifier must be annotated @Qualifier
15 | @Retention(RUNTIME)
16 | @Target({METHOD, FIELD, PARAMETER, TYPE})
17 | public @interface Healthy {
18 | // No members
19 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/qualifiers/lib/Meat.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.qualifiers.lib;
2 |
3 | /**
4 | * Meat is murder. Or producer of life in large farms. Depends on how you look
5 | * at things. In this application, we assume that eating meat is bad for human
6 | * health.
7 | *
8 | * This class is annotated with {@code @Unhealthy}.
9 | *
10 | * @author Martin Andersson (webmaster at martinandersson.com)
11 | */
12 | @Unhealthy
13 | public class Meat implements Caloric
14 | {
15 | /**
16 | * Any piece of meat has exactly 1 000 calories.
17 | *
18 | * @return {@code 1_000}
19 | */
20 | @Override
21 | public int getCalories() {
22 | return 1_000;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/qualifiers/lib/Unhealthy.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.qualifiers.lib;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.ElementType.METHOD;
5 | import static java.lang.annotation.ElementType.PARAMETER;
6 | import static java.lang.annotation.ElementType.TYPE;
7 | import java.lang.annotation.Retention;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 | import java.lang.annotation.Target;
10 | import javax.inject.Qualifier;
11 |
12 | /**
13 | * All unhealthy food are extinguished using this qualifier.
14 | *
15 | * @author Martin Andersson (webmaster at martinandersson.com)
16 | */
17 | @Qualifier // <-- Note that a qualifier must be annotated @Qualifier
18 | @Retention(RUNTIME)
19 | @Target({METHOD, FIELD, PARAMETER, TYPE})
20 | public @interface Unhealthy {
21 | // No members
22 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/qualifiers/lib/Water.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.qualifiers.lib;
2 |
3 | /**
4 | * This bean has no explicitly put qualifiers, therefore, the water bean will
5 | * get a built-in default qualifier {@code @Default}.
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | // Implicitly: @javax.enterprise.inject.Default
10 | public class Water implements Caloric
11 | {
12 | /**
13 | * Water has zero calories.
14 | *
15 | * @return {@code 0}
16 | */
17 | @Override
18 | public int getCalories() {
19 | return 0;
20 |
21 | // Or:
22 | // return Caloric.super.getCalories();
23 |
24 | // Or:
25 | // Don't implement this method at all.
26 | }
27 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/resolution/BeanTypeResolutionDriver.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.resolution;
2 |
3 | import java.io.IOException;
4 | import javax.inject.Inject;
5 | import javax.servlet.ServletException;
6 | import javax.servlet.annotation.WebServlet;
7 | import javax.servlet.http.HttpServlet;
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 |
11 | /**
12 | * @author Martin Andersson (webmaster at martinandersson.com)
13 | */
14 | @WebServlet("/BeanTypeResolutionDriver")
15 | public class BeanTypeResolutionDriver extends HttpServlet
16 | {
17 | @Inject
18 | SimpleCalculator simpleCalculator;
19 |
20 | @Override
21 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
22 | // Convert all lines in POST body to longs:
23 | long[] values = req.getReader().lines().mapToLong(Long::parseLong).toArray();
24 |
25 | // Sum and return (using the response header):
26 | long sum = simpleCalculator.sum(values);
27 | resp.setHeader("sum", String.valueOf(sum));
28 | }
29 |
30 | @Override
31 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
32 | throw new UnsupportedOperationException("Use a POST request.");
33 | }
34 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/resolution/BeanTypeResolutionTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.resolution;
2 |
3 | import com.martinandersson.javaee.utils.DeploymentBuilder;
4 | import java.io.IOException;
5 | import java.io.OutputStreamWriter;
6 | import java.net.HttpURLConnection;
7 | import java.net.MalformedURLException;
8 | import java.net.URL;
9 | import java.nio.charset.StandardCharsets;
10 | import org.jboss.arquillian.container.test.api.Deployment;
11 | import org.jboss.arquillian.container.test.api.RunAsClient;
12 | import org.jboss.arquillian.junit.Arquillian;
13 | import org.jboss.arquillian.test.api.ArquillianResource;
14 | import org.jboss.shrinkwrap.api.spec.WebArchive;
15 | import static org.junit.Assert.assertEquals;
16 | import org.junit.Test;
17 | import org.junit.runner.RunWith;
18 |
19 | /**
20 | * A common myth about CDI is that CDI can only inject interface types. Which
21 | * isn't true. Using the real class type at the injection point work flawlessly.
22 | * In fact, type is the number one "bean qualifier" that makes type safe
23 | * resolution possible.
24 | *
25 | * Note that in this deployment, there exists only one {@code SimpleCalculator}.
26 | * Had there also existed a subclass thereof, then the injection point in
27 | * {@code BeanTypeResolutionDriver} will represent an ambiguous dependency: two
28 | * bean targets match. Such deployment will almost always fail.
29 | *
30 | * @author Martin Andersson (webmaster at martinandersson.com)
31 | */
32 | @RunWith(Arquillian.class)
33 | public class BeanTypeResolutionTest {
34 |
35 | @Deployment
36 | public static WebArchive buildDeployment() {
37 | return new DeploymentBuilder(BeanTypeResolutionTest.class)
38 | .addEmptyBeansXMLFile()
39 | .add(BeanTypeResolutionDriver.class,
40 | SimpleCalculator.class)
41 | .build();
42 | }
43 |
44 | /**
45 | * Will make a HTTP POST request to {@code TypeResolutionDriver}-servlet
46 | * which use a calculator that do not implement an interface. The calculator
47 | * is a POJO as much as a POJO can be.
48 | *
49 | * @param url application url, provided by Arquillian
50 | *
51 | * @throws MalformedURLException if things go to hell
52 | * @throws IOException if things go to hell
53 | */
54 | @Test
55 | @RunAsClient
56 | public void useSimpleCalculator(@ArquillianResource URL url) throws MalformedURLException, IOException {
57 | final URL testDriver = new URL(url, BeanTypeResolutionDriver.class.getSimpleName());
58 | final HttpURLConnection conn = (HttpURLConnection) testDriver.openConnection();
59 |
60 | conn.setDoOutput(true);
61 | conn.setRequestMethod("POST");
62 | conn.setRequestProperty("Content-Type", "UTF-8");
63 | conn.setRequestProperty("Connection", "close");
64 |
65 | final long sum;
66 |
67 | try (OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8))
68 | {
69 | // Try sum 5 + 5
70 | out.write("5");
71 | out.write("\r\n");
72 | out.write("5");
73 |
74 | out.flush();
75 | sum = Long.parseLong(conn.getHeaderField("sum"));
76 | }
77 |
78 | assertEquals("Expected that 5 + 5 equals 10", 10, sum);
79 | }
80 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/resolution/GenericBeanUndeployableTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.resolution;
2 |
3 | import com.martinandersson.javaee.utils.DeploymentBuilder;
4 | import javax.enterprise.context.RequestScoped;
5 | import org.jboss.arquillian.container.test.api.Deployment;
6 | import org.jboss.arquillian.container.test.api.ShouldThrowException;
7 | import org.jboss.arquillian.junit.Arquillian;
8 | import org.jboss.shrinkwrap.api.spec.WebArchive;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 |
12 | /**
13 | * A parameterized bean class may effectively be "any type", strictly meaning
14 | * that type resolution is impossible. For example, say that such bean exists:
15 | *
16 | *
43 | *
44 | * Source (CDI 1.1 specification, section "3.1 Managed Beans"):
45 | *
46 | *
81 | *
82 | * TODO: GlassFish fail this test. Report bug.
83 | *
84 | *
85 | * @author Martin Andersson (webmaster at martinandersson.com)
86 | */
87 | @RunWith(Arquillian.class)
88 | public class GenericBeanUndeployableTest {
89 |
90 | @Deployment
91 | @ShouldThrowException // Should be: javax.enterprise.inject.spi.DefinitionException.class
92 | public static WebArchive buildDeployment() {
93 | return new DeploymentBuilder(GenericBeanUndeployableTest.class)
94 | .addEmptyBeansXMLFile()
95 | .add(GenericBeanRequestScoped.class)
96 | .build();
97 | }
98 |
99 | @Test // <-- implicitly @RunAsClient given that @ShouldThrowException is put on @Deployment
100 | public void genericBeanMustBeDependent() { }
101 | }
102 |
103 | /**
104 | * A parameterized bean class with a normal scope equals definition error.
105 | *
106 | * @author Martin Andersson (webmaster at martinandersson.com)
107 | */
108 | @RequestScoped
109 | class GenericBeanRequestScoped
18 | *
19 | * Results: For WildFly and GlassFish, the CDI singleton proxy do not serialize
20 | * concurrent calls.
21 | *
22 | * Warning! GlassFish takes about a full minute on my machine
23 | * to execute this test. WildFly 8.1.0 take 5 seconds =)
24 | *
25 | * @author Martin Andersson (webmaster at martinandersson.com)
26 | */
27 | @RunWith(Arquillian.class)
28 | public class ApplicationScopedConcurrencyTest {
29 | @Deployment
30 | public static WebArchive buildDeployment() {
31 | return new DeploymentBuilder(ApplicationScopedConcurrencyTest.class)
32 | .addEmptyBeansXMLFile()
33 | .add(ConcurrentInvocationCounter.class,
34 | PhasedExecutorService.class)
35 | .build();
36 | }
37 |
38 | @Inject
39 | ConcurrentInvocationCounter bean;
40 |
41 | @Resource
42 | ManagedThreadFactory threadFactory;
43 |
44 | @Test
45 | public void applicationScopedBeanIsUnsynchronized() {
46 |
47 | PhasedExecutorService executor = new PhasedExecutorService(threadFactory);
48 | final int threadCount = executor.getThreadCount();
49 |
50 | executor.invokeManyTimes(bean::sleepOneSecond, threadCount);
51 |
52 | assertEquals("Expected all but one thread to arrive concurrently.",
53 | threadCount - 1, ConcurrentInvocationCounter.getConcurrentCallsCount());
54 |
55 | // TODO: Move to own test:
56 |
57 | /*
58 | * TWO instances!? Containers usually create at least one extra for
59 | * inspection of the bean, then a another clean instance that is the one
60 | * receiving client calls. I've seen EJB containers create a LOT MORE
61 | * than just one extra! Here though, WildFly and GlassFish only create
62 | * one extra and I dare to hardcode a number 2.
63 | */
64 | assertEquals("Expected that two instances was created.",
65 | 2, ConcurrentInvocationCounter.getInstancesCreated());
66 |
67 | // .. but the containers must not let the application think there are more than one instance:
68 | assertEquals("Expected only one @PostConstruct callback.",
69 | 1, ConcurrentInvocationCounter.getLogicalInstancesCreated());
70 | }
71 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/scope/application/ConcurrentInvocationCounter.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.scope.application;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import java.util.concurrent.atomic.AtomicBoolean;
5 | import java.util.concurrent.atomic.LongAdder;
6 | import java.util.logging.Logger;
7 | import javax.annotation.PostConstruct;
8 | import javax.enterprise.context.ApplicationScoped;
9 |
10 | /**
11 | * @author Martin Andersson (webmaster at martinandersson.com)
12 | */
13 | @ApplicationScoped
14 | public class ConcurrentInvocationCounter
15 | {
16 | private static final LongAdder instances = new LongAdder(),
17 | logicalInstances = new LongAdder(),
18 | concurrentCalls = new LongAdder();
19 |
20 | private final AtomicBoolean isSleeping = new AtomicBoolean();
21 |
22 |
23 |
24 | public static long getInstancesCreated() {
25 | return instances.sum();
26 | }
27 |
28 | public static long getLogicalInstancesCreated() {
29 | return logicalInstances.sum();
30 | }
31 |
32 | public static long getConcurrentCallsCount() {
33 | return concurrentCalls.sum();
34 | }
35 |
36 |
37 |
38 | public void sleepOneSecond() {
39 | if (isSleeping.getAndSet(true)) {
40 | concurrentCalls.increment();
41 | return;
42 | }
43 |
44 | try {
45 | TimeUnit.SECONDS.sleep(1);
46 | }
47 | catch (InterruptedException e) {
48 | Logger.getLogger(ConcurrentInvocationCounter.class.getName())
49 | .warning("Interrupted from sleep.");
50 | }
51 | finally {
52 | isSleeping.set(false);
53 | }
54 | }
55 |
56 |
57 |
58 | {
59 | instances.increment();
60 | }
61 |
62 | @PostConstruct
63 | private void __postConstruct() {
64 | logicalInstances.increment();
65 | }
66 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/scope/request/ApplicationScopedBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.scope.request;
2 |
3 | import javax.enterprise.context.ApplicationScoped;
4 | import javax.inject.Inject;
5 |
6 | /**
7 | * An {@code @ApplicationScoped} bean.
8 | *
9 | * @author Martin Andersson (webmaster at martinandersson.com)
10 | */
11 | @ApplicationScoped
12 | public class ApplicationScopedBean
13 | {
14 | @Inject
15 | RequestScopedBean bean;
16 |
17 | public int getIdOfNestedRequestedScopedBean() {
18 | return bean.getId();
19 | }
20 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/scope/request/RequestScopedBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.scope.request;
2 |
3 | import javax.enterprise.context.RequestScoped;
4 | import javax.inject.Inject;
5 |
6 | /**
7 | * A {@code @RequestScoped} bean.
8 | *
9 | * @author Martin Andersson (webmaster at martinandersson.com)
10 | */
11 | @RequestScoped
12 | public class RequestScopedBean
13 | {
14 | @Inject
15 | RequestScopedBean nestedBean;
16 |
17 | /**
18 | * The Id is computed using the memory based hash code of the instance;
19 | * which is the default implementation for Object.hashCode().
20 | *
21 | * However, the hash and therefore this Id is not gauranteed to be unique.
22 | * The alternative would be for us to use a {@code AtomicInteger} or any
23 | * other similar construct for generating a true Id. However, these
24 | * constructs inevitably lower the degree of parallelism which is why I
25 | * choose the hash based version. If problems arise, lower the amount of
26 | * beans created, force them to stay in-memory longer, write another Id
27 | * implementation, or write another bean: for example one that crash if
28 | * concurrent requests are made (if so, simulate work in-bean).
29 | *
30 | * @return id of the bean
31 | */
32 | public int getId() {
33 | /*
34 | * So why not use Object.hashCode() or make the TestDriver call that
35 | * method directly? I actually tried the latter option but discovered
36 | * that sometimes, the call wasn't routed to the bean by GlassFish
37 | * (didn't test WildFly) and produced what seemed to be an arbitrary
38 | * number which unnecessarily failed the test. Hence the explicit
39 | * naming of the Id-method and this alternative implementation.
40 | */
41 | return System.identityHashCode(this);
42 | }
43 |
44 | public int getIdOfNestedRequestedScopedBean() {
45 | return nestedBean.getId();
46 | }
47 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/scope/request/StatelessBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.scope.request;
2 |
3 | import java.util.concurrent.Future;
4 | import javax.ejb.AsyncResult;
5 | import javax.ejb.Asynchronous;
6 | import javax.ejb.Stateless;
7 | import javax.inject.Inject;
8 |
9 | /**
10 | * A {@code @Stateless} bean.
11 | *
12 | * @author Martin Andersson (webmaster at martinandersson.com)
13 | */
14 | @Stateless
15 | public class StatelessBean
16 | {
17 | /*
18 | * Although possible without any logical errors, does it make sense to
19 | * inject client-specific state into a stateless bean here? I don't know.
20 | */
21 | @Inject
22 | RequestScopedBean bean;
23 |
24 | public int getIdOfNestedRequestedScopedBean() {
25 | return bean.getId();
26 | }
27 |
28 | @Asynchronous
29 | public Future
26 | *
27 | * Although all examples online use a specialized {@code @Alternative} bean, the
28 | * specification says not a word that a specialized bean must also be an
29 | * {@code @Alternative}.
30 | *
31 | * A specialized CDI bean fully replaces his counterpart and the developer
32 | * doesn't have to reconfigure injection points that originally saw the
33 | * generalized bean.
34 | *
35 | *
36 | *
37 | *
41 | *
42 | * CDI 1.1 specification, section "4.3 Specialization":
43 | *
16 | *
17 | * To continue on the example provided in
18 | * {@linkplain com.martinandersson.javaee.cdi.specializes.lib.StupidCalculator StupidCalculator},
19 | * had the other corporate division been obsessed with marking classes as final,
20 | * then good luck to you.
21 | *
22 | * CDI 1.1 "3.1.4 Specializing a managed bean":
23 | *
17 | *
18 | * Use specialization.
19 | *
20 | * @author Martin Andersson (webmaster at martinandersson.com)
21 | */
22 | public class StupidCalculator
23 | {
24 | private static final Logger LOGGER = Logger.getLogger(StupidCalculator.class.getName());
25 |
26 | public static final LongAdder OBSERVER_COUNTER = new LongAdder(),
27 | INSTANCE_COUNTER = new LongAdder();
28 |
29 | {
30 | LOGGER.info(() ->
31 | "instance created " +
32 | "(hash: " + System.identityHashCode(this) + ", bean class: " + getClass().getSimpleName() + ")");
33 |
34 | INSTANCE_COUNTER.increment();
35 | }
36 |
37 |
38 |
39 | /**
40 | * A slow summing function.
41 | *
42 | * @param values to sum
43 | * @return sum
44 | */
45 | public long sum(long... values) {
46 | try {
47 | TimeUnit.SECONDS.sleep(1);
48 | }
49 | catch (InterruptedException e) {
50 | ;
51 | }
52 |
53 | return LongStream.of(values).sum();
54 | }
55 |
56 |
57 | @PostConstruct
58 | private void __postConstruct() {
59 | LOGGER.info(() ->
60 | "__postConstruct() called (hash: " + System.identityHashCode(this) + ", bean class: " + getClass().getSimpleName() + ")");
61 | }
62 |
63 | private void __instanceObserve(@Observes String text) {
64 | LOGGER.warning(() ->
65 | "__instanceObserve() called with text: " + text +
66 | " (hash: " + System.identityHashCode(this) + ", bean class: " + getClass().getSimpleName() + ")");
67 |
68 | OBSERVER_COUNTER.increment();
69 | }
70 |
71 | private static void __staticObserve(@Observes String text) {
72 | LOGGER.warning(() -> "staticObserve() called with text: " + text);
73 | OBSERVER_COUNTER.increment();
74 | }
75 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/specializes/lib/UserSettings.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.specializes.lib;
2 |
3 | /**
4 | * @author Martin Andersson (webmaster at martinandersson.com)
5 | */
6 | public interface UserSettings {
7 | boolean allowMultipleDevices();
8 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/transactional/CrashingBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.transactional;
2 |
3 | import java.io.IOException;
4 | import java.util.Objects;
5 | import javax.annotation.Resource;
6 | import javax.transaction.Status;
7 | import javax.transaction.Synchronization;
8 | import javax.transaction.TransactionSynchronizationRegistry;
9 | import javax.transaction.Transactional;
10 | import static org.junit.Assert.assertEquals;
11 |
12 | /**
13 | * A {@code @Transactional} CDI bean that always crash.
14 | *
15 | * @author Martin Andersson (webmaster at martinandersson.com)
16 | */
17 | @Transactional
18 | public class CrashingBean {
19 |
20 | private static volatile Boolean transactionRolledback;
21 |
22 | public static boolean transactionRolledBack() {
23 | try {
24 | return Objects.requireNonNull(transactionRolledback, "Which transaction??");
25 | }
26 | finally {
27 | transactionRolledback = null;
28 | }
29 | }
30 |
31 |
32 |
33 | @Resource(lookup = "java:comp/TransactionSynchronizationRegistry")
34 | TransactionSynchronizationRegistry reg;
35 |
36 |
37 |
38 | /*
39 | * --------------------
40 | * | UNCHECKED THROWERS |
41 | * --------------------
42 | */
43 |
44 | public void throwNPE_noThrowsClause() {
45 | assertInTx();
46 |
47 | /*
48 | * We expect client to invoke this method without a transaction and then
49 | * use CrashingBean.transactionRolledBack() to query for the transaction
50 | * status. Thus:
51 | */
52 | registerRollbackListener();
53 |
54 | throw new NullPointerException("Oops.");
55 | }
56 |
57 | /*
58 | * We expect client to invoke this method with an active transaction and
59 | * then query his own transaction for status. So in this method, we do not
60 | * register the rollback listener but we get his transaction key and assert
61 | * we really did inherit the same transaction.
62 | */
63 | public void throwNPE_noThrowsClause(Object transactionKey) {
64 | assertCallerTx(transactionKey);
65 | throw new NullPointerException("Oops.");
66 | }
67 |
68 | public void throwNPE_hasThrowsClause() throws NullPointerException {
69 | assertInTx();
70 | registerRollbackListener();
71 | throw new NullPointerException("Oops.");
72 | }
73 |
74 | public void throwNPE_hasThrowsClause(Object transactionKey) throws NullPointerException {
75 | assertCallerTx(transactionKey);
76 | throw new NullPointerException("Oops.");
77 | }
78 |
79 |
80 |
81 | /*
82 | * ------------------
83 | * | CHECKED THROWERS |
84 | * ------------------
85 | */
86 |
87 | public void throwIOException() throws IOException {
88 | assertInTx();
89 | registerRollbackListener();
90 | throw new IOException("Oops.");
91 | }
92 |
93 | public void throwIOException(Object transactionKey) throws IOException {
94 | assertCallerTx(transactionKey);
95 | throw new IOException("Oops.");
96 | }
97 |
98 |
99 |
100 | /*
101 | * --------------
102 | * | INTERNAL API |
103 | * --------------
104 | */
105 |
106 | private void assertInTx() {
107 | assertEquals(Status.STATUS_ACTIVE, reg.getTransactionStatus());
108 | }
109 |
110 | private void assertCallerTx(Object transactionKey) {
111 | assertInTx();
112 | assertEquals(transactionKey, reg.getTransactionKey());
113 | }
114 |
115 | private void registerRollbackListener() {
116 | reg.registerInterposedSynchronization(new Synchronization(){
117 | @Override public void beforeCompletion() { /* ignored */ }
118 | @Override public void afterCompletion(int status) {
119 | transactionRolledback = (status == Status.STATUS_ROLLEDBACK);
120 | }
121 | });
122 | }
123 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/exceptions/CrashingBean1.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.exceptions;
2 |
3 | import javax.annotation.Resource;
4 | import javax.ejb.Stateless;
5 | import javax.ejb.TransactionAttribute;
6 | import javax.ejb.TransactionAttributeType;
7 | import javax.transaction.Synchronization;
8 | import javax.transaction.TransactionSynchronizationRegistry;
9 |
10 | /**
11 | * A crashing bean.
12 | *
13 | * All methods throw {@code ArithmeticException}. Transactional methods accept
14 | * a boolean {@code deferred} which if it is false, shall cause the bean to
15 | * crash immediately, otherwise the crash will be deferred until transaction
16 | * commit.
17 | *
18 | * @author Martin Andersson (webmaster at martinandersson.com)
19 | */
20 | @Stateless
21 | public class CrashingBean1
22 | {
23 | @Resource
24 | TransactionSynchronizationRegistry reg;
25 |
26 | @TransactionAttribute(TransactionAttributeType.NEVER)
27 | public void crash_neverTx() {
28 | int crash = 1 / 0;
29 | }
30 |
31 | @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
32 | public void crash_requiresNewTx(boolean deferred) {
33 | crash(deferred);
34 | }
35 |
36 | @TransactionAttribute(TransactionAttributeType.MANDATORY)
37 | public void crash_mandatoryTx(boolean deferred) {
38 | crash(deferred);
39 | }
40 |
41 | private void crash(boolean deferred) {
42 | if (deferred) {
43 | reg.registerInterposedSynchronization(new Synchronization() {
44 | @Override public void beforeCompletion() {
45 | int crash = 1 / 0; }
46 | @Override public void afterCompletion(int status) { }
47 | });
48 | }
49 | else {
50 | int crash = 1 / 0;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/exceptions/CrashingBean2.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.exceptions;
2 |
3 | import javax.ejb.LocalBean;
4 | import javax.ejb.Stateless;
5 |
6 | /**
7 | *
8 | * @author Martin Andersson (webmaster at martinandersson.com)
9 | */
10 | @Stateless
11 | @LocalBean
12 | public class CrashingBean2 implements RemoteCrashingBean
13 | {
14 | /** {@inheritDoc} */
15 | @Override
16 | public void uncheckedSystemException() {
17 | throw new RuntimeException("123456");
18 | }
19 |
20 | /** {@inheritDoc} */
21 | @Override
22 | public void uncheckedSystemException_declared() throws RuntimeException {
23 | throw new RuntimeException("123456");
24 | }
25 |
26 | /** {@inheritDoc} */
27 | @Override
28 | public void uncheckedApplicationException() {
29 | throw new CustomApplicationException("123456");
30 | }
31 |
32 | /** {@inheritDoc} */
33 | @Override
34 | public void checkedException() throws Exception {
35 | throw new Exception("123456");
36 | }
37 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/exceptions/CustomApplicationException.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.exceptions;
2 |
3 | import javax.ejb.ApplicationException;
4 |
5 | /**
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | @ApplicationException
10 | public class CustomApplicationException extends RuntimeException
11 | {
12 | public CustomApplicationException(String msg) {
13 | super(msg);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/exceptions/RemoteCrashingBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.exceptions;
2 |
3 | import javax.ejb.Remote;
4 |
5 | /**
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | @Remote
10 | public interface RemoteCrashingBean
11 | {
12 | /**
13 | * This method will throw a {@code RuntimeException} with a message set:
14 | * "123456".
15 | */
16 | public void uncheckedSystemException();
17 |
18 | /**
19 | * This method will throw a {@code RuntimeException} with a message set:
20 | * "123456".
21 | */
22 | public void uncheckedSystemException_declared() throws RuntimeException;
23 |
24 | /**
25 | * This method will throw a {@code CustomApplicationException} with a
26 | * message set: "123456".
27 | */
28 | public void uncheckedApplicationException();
29 |
30 | /**
31 | * This method will throw a normal {@code Exception} with a message set:
32 | * "123456".
33 | *
34 | * @throws Exception always
35 | */
36 | public void checkedException() throws Exception;
37 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/exceptions/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Currently, this package offer two tests related to EJB:s and exceptions
3 | * thrown by them.
4 | *
5 | * {@linkplain com.martinandersson.javaee.ejb.exceptions.EJBTransactionRolledbackExceptionTest}
6 | * examine when the client can expect to see an
7 | * {@code EJBTransactionRolledbackException}.
8 | *
9 | * {@linkplain com.martinandersson.javaee.ejb.exceptions.SystemAndApplicationExceptionTest}
10 | * examine the difference between EJB system-level exceptions and EJB
11 | * application applications, as well as look into whether or not using a
12 | * colocated {@code @Remote} EJB makes any difference.
13 | */
14 | package com.martinandersson.javaee.ejb.exceptions;
15 |
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/inheritance/A.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.inheritance;
2 |
3 | import javax.ejb.Stateless;
4 |
5 | /**
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | @Stateless
10 | public class A {
11 | public String simpleName() {
12 | return "A";
13 | }
14 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/inheritance/B.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.inheritance;
2 |
3 | import javax.ejb.Stateless;
4 |
5 | /**
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | @Stateless
10 | public class B extends A {
11 | @Override public String simpleName() {
12 | return "B";
13 | }
14 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/inheritance/C.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.inheritance;
2 |
3 | import javax.ejb.Stateless;
4 | import javax.enterprise.inject.Specializes;
5 |
6 | /**
7 | *
8 | * @author Martin Andersson (webmaster at martinandersson.com)
9 | */
10 | @Stateless
11 | @Specializes
12 | public class C extends A {
13 | @Override public String simpleName() {
14 | return "C";
15 | }
16 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/inheritance/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * 1. Session bean inheritance. Only for code reuse. So what happens if I let
3 | * session bean A extend session bean B? Which implementation do I get when
4 | * looking up the no-interface view of A?
5 | *
6 | * 2. EJB section "4.9.2.1 Session Bean Superclasses" says that the view of a
7 | * superclass is not inherited unless explicitly put in the interface
8 | * declaration clause by the subclass. So what happens if client to call a
9 | * superclass method? Is the call not managed, will it not be directed through a
10 | * proxy? Will it crash?
11 | *
12 | * 2. Using CDI:s @Specializes on EJB:s are permitted (CDI 1.2, section 3.2.4).
13 | * So must the client code use @Inject? @EJB may find the "vetoed" bean? Note
14 | * that @Specializes is a CDI construct!
15 | * FINDING GF: The "vetoed" bean is not discoverable any more even if using
16 | * @EJB. Test will crash during RUNTIME if the vetoed bean is tried to be called.
17 | * There will be a NPE deep down in the Weld layer and the client see a
18 | * EJBException.
19 | */
20 | package com.martinandersson.javaee.ejb.inheritance;
21 |
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/SingletonBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans;
2 |
3 | import javax.inject.Singleton;
4 |
5 | /**
6 | * This bean is annotated {@linkplain Singleton @Singleton} but has no other
7 | * annotations applied. Implicitly therefore, following default annotations are
8 | * in effect:
9 | *
10 | *
22 | *
23 | * If the client never invoke this method, then the life of an idle bean
24 | * will linger on until a timeout happen which causes the server to discard
25 | * the bean. How long before that timeout happen is vendor-specific.
26 | *
27 | * Read more in a source-code comment written in the client code of {@code
28 | * TestDriver.__removeStatefulBeans()}.
29 | */
30 | @Remove
31 | public void remove() {
32 | // Work done.
33 | }
34 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/StatelessBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans;
2 |
3 | /**
4 | * @author Martin Andersson (webmaster at martinandersson.com)
5 | */
6 | @javax.ejb.Stateless
7 | public class StatelessBean extends AbstractSessionBean {
8 | // All business methods provided by AbstractSessionBean
9 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * TODO: Write something.
3 | */
4 | package com.martinandersson.javaee.ejb.sessionbeans;
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/testdriver/EJBType.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans.testdriver;
2 |
3 | /**
4 | * Represent an Enterprise JavaBean session bean type. Used by {@linkplain
5 | * ExecutionSettings} to configure a test run on {@linkplain TestDriver}.
6 | *
7 | * @see Operation
8 | *
9 | * @author Martin Andersson (webmaster at martinandersson.com)
10 | */
11 | public enum EJBType {
12 | SINGLETON, STATEFUL, STATELESS
13 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/testdriver/ExecutionSettings.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans.testdriver;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Configuration for a test executed by {@linkplain TestDriver}.
7 | *
8 | * @see Operation
9 | * @see EJBType
10 | *
11 | * @author Martin Andersson (webmaster at martinandersson.com)
12 | */
13 | public class ExecutionSettings implements Serializable {
14 | private final Operation operation;
15 | private final EJBType type;
16 |
17 | public ExecutionSettings(Operation operation, EJBType type) {
18 | this.operation = operation;
19 | this.type = type;
20 | }
21 |
22 | public Operation getOperation() {
23 | return operation;
24 | }
25 |
26 | public EJBType getEJBType() {
27 | return type;
28 | }
29 |
30 | @Override
31 | public String toString() {
32 | return new StringBuilder(ExecutionSettings.class.getSimpleName())
33 | .append("[")
34 | .append("operation=").append(operation)
35 | .append(", type=").append(type)
36 | .append("]")
37 | .toString();
38 | }
39 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/testdriver/Operation.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans.testdriver;
2 |
3 | /**
4 | * Defines how selected EJB references shall be invoked to fetch their bean id.
5 | *
6 | * Current values:
7 | *
8 | *
14 | *
15 | * As usual, don't expect that only one instance will ever be created.
16 | * Technically, many instances may be created by the EJB container for runtime
17 | * inspection. All client calls go to the same instance though and
18 | * {@code @PostConstruct} is called just once on the "real" instance used.
19 | *
20 | * Read more in the {@linkplain SingletonBean JavaDoc of SingletonBean}.
21 | *
22 | * @author Martin Andersson (webmaster at martinandersson.com)
23 | */
24 | public class SingletonTest extends AbstractSessionTest
25 | {
26 | public SingletonTest() {
27 | super(EJBType.SINGLETON);
28 | }
29 |
30 |
31 |
32 | @Test
33 | @RunAsClient
34 | public void bothReferencesGoToSameBean() {
35 | Report report = super.run(Operation.CALL_TWO_SERIALLY);
36 |
37 | assertEquals("Only one @Singleton instance was supposed to be used, ",
38 | report.beanId1, report.beanId2);
39 | }
40 |
41 | @Test
42 | @RunAsClient
43 | public void sameReferenceGoToSameBean() {
44 | Report report = run(Operation.CALL_ONE_SERIALLY);
45 |
46 | assertEquals("Only one @Singleton instance was supposed to be used, ",
47 | report.beanId1, report.beanId2);
48 | }
49 |
50 | @Test
51 | @RunAsClient
52 | public void concurrentInvocationsGoToSameBean() {
53 | Report report = run(Operation.CALL_ONE_CONCURRENTLY);
54 |
55 | assertEquals("Only one @Singleton instance was supposed to be used, ",
56 | report.beanId1, report.beanId2);
57 | }
58 |
59 | /**
60 | * Given that clients only have one @Singleton bean instance to use, the
61 | * self invoking pattern will cause a loopback call, which is allowed for
62 | * singletons.
63 | *
64 | * EJB 3.2 specification, section "4.8.5 Singleton Session Bean Concurrency":
65 | * {@code
26 | *
27 | * [..] obtain a contextual reference for the bean [..].
28 | *
29 | * The method destroy() instructs the container to destroy the instance. The
30 | * bean instance passed to destroy() should be a dependent scoped bean
31 | * instance, or a client proxy for a normal scoped bean. Applications are
32 | * encouraged to always call destroy() when they no longer require an
33 | * instance obtained from Instance. All built-in normal scoped contexts
34 | * support destroying bean instances. An UnsupportedOperationException is
35 | * thrown if the active context object for the scope type of the bean does
36 | * not support destroying bean instances.
37 | * }
38 | *
39 | * @author Martin Andersson (webmaster at martinandersson.com)
40 | */
41 | @RunWith(Arquillian.class)
42 | @RunAsClient
43 | public class InstanceVersusProviderTest
44 | {
45 | @Deployment
46 | private static Archive> buildDeployment() {
47 | return new DeploymentBuilder(InstanceVersusProviderTest.class)
48 | .add(AbstractId.class,
49 | RequestScopedBean.class,
50 | InstanceVersusProviderRunner.class)
51 | .build();
52 | }
53 |
54 | @ArquillianResource
55 | URL url;
56 |
57 | static long[] lastIds;
58 |
59 | @Test
60 | public void test_callInstanceFirstThenProvider() {
61 | long[] Ids = HttpRequests.getObject(url,
62 | new RequestParameter("callFirst", CallFirst.INSTANCE));
63 |
64 | assertEquals(Ids[0], Ids[1]);
65 |
66 | if (lastIds == null) {
67 | lastIds = Ids;
68 | }
69 | else {
70 | assertNotEquals(Ids[0], lastIds[0]);
71 | }
72 | }
73 |
74 | @Test
75 | public void test_callProviderFirstThenInstance() {
76 | long[] Ids = HttpRequests.getObject(url,
77 | new RequestParameter("callFirst", CallFirst.PROVIDER));
78 |
79 | assertEquals(Ids[0], Ids[1]);
80 |
81 | if (lastIds == null) {
82 | lastIds = Ids;
83 | }
84 | else {
85 | assertNotEquals(Ids[0], lastIds[0]);
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/ProgrammaticLookupRunner.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import java.io.IOException;
4 | import java.io.ObjectOutputStream;
5 | import javax.enterprise.inject.spi.CDI;
6 | import javax.servlet.ServletException;
7 | import javax.servlet.annotation.WebServlet;
8 | import javax.servlet.http.HttpServlet;
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.servlet.http.HttpServletResponse;
11 |
12 | @WebServlet("")
13 | public class ProgrammaticLookupRunner extends HttpServlet
14 | {
15 | private final RequestScopedBean requestScoped
16 | = CDI.current().select(RequestScopedBean.class).get();
17 |
18 | private final ApplicationScopedBean applicationScoped
19 | = CDI.current().select(ApplicationScopedBean.class).get();
20 |
21 | private final DependentBean dependentBean1
22 | = CDI.current().select(DependentBean.class).get();
23 |
24 | private final DependentBean dependentBean2
25 | = CDI.current().select(DependentBean.class).get();
26 |
27 | @Override
28 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
29 | try (ObjectOutputStream out = new ObjectOutputStream(resp.getOutputStream())) {
30 | out.writeObject(new long[]{
31 | requestScoped.getId(),
32 | applicationScoped.getId(),
33 | dependentBean1.getId(),
34 | dependentBean2.getId()});
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/ProgrammaticLookupTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import com.martinandersson.javaee.utils.DeploymentBuilder;
4 | import com.martinandersson.javaee.utils.HttpRequests;
5 | import java.net.URL;
6 | import org.jboss.arquillian.container.test.api.Deployment;
7 | import org.jboss.arquillian.container.test.api.RunAsClient;
8 | import org.jboss.arquillian.junit.Arquillian;
9 | import org.jboss.arquillian.junit.InSequence;
10 | import org.jboss.arquillian.test.api.ArquillianResource;
11 | import org.jboss.shrinkwrap.api.Archive;
12 | import static org.junit.Assert.assertEquals;
13 | import static org.junit.Assert.assertNotEquals;
14 | import static org.junit.Assert.assertTrue;
15 | import org.junit.Test;
16 | import org.junit.runner.RunWith;
17 |
18 | /**
19 | * Demonstrates CDI.current().select() and show that using the CDI
20 | * container programmatically to lookup a bean will provide the client code with
21 | * a contextual (scoped) instance of the target bean.{@code
25 | * http://stackoverflow.com/q/15067650
26 | * http://stackoverflow.com/q/24822361
27 | * }
28 | *
29 | * @author Martin Andersson (webmaster at martinandersson.com)
30 | */
31 | @RunWith(Arquillian.class)
32 | @RunAsClient
33 | public class ProgrammaticLookupTest
34 | {
35 | @Deployment
36 | private static Archive> buildDeployment() {
37 | return new DeploymentBuilder(ProgrammaticLookupTest.class)
38 | .add(AbstractId.class,
39 | ApplicationScopedBean.class,
40 | DependentBean.class,
41 | RequestScopedBean.class,
42 | ProgrammaticLookupRunner.class)
43 | .addEmptyBeansXMLFile()
44 | .build();
45 | }
46 |
47 | static long requestScopedId,
48 | applicationScopedId,
49 | dependentId1,
50 | dependentId2;
51 |
52 | @ArquillianResource
53 | URL url;
54 |
55 | @Test
56 | @InSequence(1)
57 | public void before() {
58 | long[] ids = HttpRequests.getObject(url);
59 |
60 | requestScopedId = ids[0];
61 | applicationScopedId = ids[1];
62 | dependentId1 = ids[2];
63 | dependentId2 = ids[3];
64 |
65 | assertTrue(requestScopedId > 1);
66 | assertTrue(applicationScopedId > 1);
67 | assertTrue(dependentId1 > 1);
68 | assertTrue(dependentId2 > 1);
69 |
70 | assertNotEquals(requestScopedId, applicationScopedId);
71 | assertNotEquals(applicationScopedId, dependentId1);
72 | }
73 |
74 | @Test
75 | @InSequence(2)
76 | public void testDependent1MustNotBeDependent2() {
77 | assertNotEquals("Each new lookup of a @Dependent should provide a new bean, ",
78 | dependentId1, dependentId2);
79 | }
80 |
81 | @Test
82 | @InSequence(3)
83 | public void testBeanIdsOfSecondRequest() {
84 | long[] ids = HttpRequests.getObject(url);
85 |
86 | assertNotEquals("One new @RequestScoped bean per request, ",
87 | requestScopedId, ids[0]);
88 |
89 | assertEquals("@ApplicationScoped bean is a singleton, ",
90 | applicationScopedId, ids[1]);
91 |
92 | assertEquals("@Dependent 1 must not be replaced, ",
93 | dependentId1, ids[2]);
94 |
95 | assertEquals("@Dependent 2 must not be replaced, ",
96 | dependentId2, ids[3]);
97 | }
98 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/RequestScopedBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.lookup;
2 |
3 | import javax.enterprise.context.RequestScoped;
4 |
5 | @RequestScoped
6 | public class RequestScopedBean extends AbstractId {
7 | // Empty
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/lookup/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Package of tests that explore topics surrounding the lookup of beans.
3 | */
4 | package com.martinandersson.javaee.cdi.lookup;
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/packaging/ExplicitPackageTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.packaging;
2 |
3 | import com.martinandersson.javaee.cdi.packaging.lib.CalculatorManagedBean;
4 | import com.martinandersson.javaee.cdi.packaging.lib.CalculatorRequestScoped;
5 | import com.martinandersson.javaee.cdi.packaging.lib.CalculatorUnAnnotated;
6 | import com.martinandersson.javaee.utils.DeploymentBuilder;
7 | import javax.inject.Inject;
8 | import org.jboss.arquillian.container.test.api.Deployment;
9 | import org.jboss.arquillian.junit.Arquillian;
10 | import org.jboss.shrinkwrap.api.spec.WebArchive;
11 | import static org.junit.Assert.assertEquals;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 |
15 | /**
16 | * This test will package an empty {@code beans.xml} descriptor file in the
17 | * deployment archive, meaning that the archive is an "explicit bean archive" in
18 | * which all classes are eligible for CDI management.GlassFish bug #1
21 | *
22 | * Scope types are "bean defining annotations". WildFly and my understanding of
23 | * all related Java EE specifications also make
24 | * {@code @javax.annotation.ManagedBean} a bean defining annotation. GlassFish
25 | * disagree. I posted a question about it on
26 | * Stackoverflow.com
27 | * but until the final verdict arrive, I leave out injection of a
28 | * {@code @ManagedBean} from this test as it will crash GlassFish.GlassFish bug #2
33 | *
34 | * No GlassFish version that I've tried (4.0.1-b08 and 4.1) doesn't want to
35 | * pickup the CDI inspector. GF 4.0.1-b08 pass the test with the CDI inspector
36 | * installed. GlassFish 4.1 however doesn't ({@code requestScoped == null}).
37 | * Looking in the log reveal a suspicious message: "BeanManager not found".
38 | * Adding the {@code beans.xml} file would solve this problem (still not pick up
39 | * the CDI inspector) but that would make this archive an explicit one. For now,
40 | * I'll leave the CDI inspector out of the game.{@code
16 | *
17 | * https://svn.java.net/svn/javaeetutorial~svn/branches/javaee-tutorial-7.0.5/examples/cdi/producerfields/src/main/java/javaeetutorial/producerfields/db/UserDatabaseEntityManager.java
18 | *
19 | * }
20 | *
21 | * @author Martin Andersson (webmaster at martinandersson.com)
22 | */
23 | @Singleton
24 | public class OracleProducer
25 | {
26 | /*
27 | * JLS $17.4.5:
28 | *
29 | * "A call to start() on a thread happens-before any actions in the
30 | * started thread."
31 | *
32 | * Hence disposeException is not marked volatile and we can be safe (like
33 | * 100 %) knowing an exception will not leak to the other thread.
34 | */
35 | private static RuntimeException disposeException;
36 |
37 | public static RuntimeException consumeDisposeException() {
38 | try {
39 | return disposeException;
40 | }
41 | finally {
42 | disposeException = null;
43 | }
44 | }
45 |
46 |
47 | private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();
48 |
49 | public static final int numberOfInstancesCreated() {
50 | return INSTANCE_COUNTER.get();
51 | }
52 |
53 |
54 | public OracleProducer() {
55 | INSTANCE_COUNTER.incrementAndGet();
56 | }
57 |
58 |
59 | @Produces
60 | @UserDatabase
61 | @PersistenceContext
62 | private EntityManager em;
63 |
64 | @PersistenceContext
65 | private EntityManager forComparison;
66 |
67 |
68 | @PostConstruct
69 | private void __logEntityManager() {
70 | OracleTestDriver.log("PRODUCER-POSTCONSTRUCT JPA PROXY", em);
71 | OracleTestDriver.log("PRODUCER-POSTCONSTRUCT FOR COMPARISON", forComparison);
72 | }
73 |
74 |
75 | public void close(@Disposes @UserDatabase EntityManager em) {
76 | OracleTestDriver.log("DISPOSE CDI PROXY", em);
77 | OracleTestDriver.log("DISPOSE JPA PROXY", em.unwrap(EntityManager.class));
78 |
79 | try {
80 | em.close();
81 | disposeException = null;
82 | }
83 | catch (RuntimeException e) {
84 | Logger.getLogger("TEST").info(() ->
85 | Thread.currentThread().getName() + " DISPOSER CAUGHT EXCEPTION: " + e);
86 |
87 | disposeException = e;
88 |
89 | /*
90 | * Weld will log a "WELD-000019: Error destroying an instance.." but
91 | * the cause is lost.
92 | */
93 | throw e;
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/unsafe/UserDatabase.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.producers.entitymanager.unsafe;
2 |
3 | import static java.lang.annotation.ElementType.FIELD;
4 | import static java.lang.annotation.ElementType.METHOD;
5 | import static java.lang.annotation.ElementType.PARAMETER;
6 | import static java.lang.annotation.ElementType.TYPE;
7 | import java.lang.annotation.Retention;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 | import java.lang.annotation.Target;
10 | import javax.inject.Qualifier;
11 |
12 | /**
13 | *
14 | * @author Martin Andersson (webmaster at martinandersson.com)
15 | */
16 | @Qualifier
17 | @Retention(RUNTIME)
18 | @Target({METHOD, FIELD, PARAMETER, TYPE})
19 | public @interface UserDatabase
20 | {
21 | // Empty
22 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/qualifiers/QualifierDriver.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.qualifiers;
2 |
3 | import com.martinandersson.javaee.cdi.qualifiers.lib.Healthy;
4 | import com.martinandersson.javaee.cdi.qualifiers.lib.Caloric;
5 | import com.martinandersson.javaee.cdi.qualifiers.lib.Unhealthy;
6 | import java.io.IOException;
7 | import java.io.ObjectOutputStream;
8 | import java.io.Serializable;
9 | import javax.inject.Inject;
10 | import javax.servlet.ServletException;
11 | import javax.servlet.annotation.WebServlet;
12 | import javax.servlet.http.HttpServlet;
13 | import javax.servlet.http.HttpServletRequest;
14 | import javax.servlet.http.HttpServletResponse;
15 |
16 | /**
17 | * The interface a bean implement, is also part of the bean type. So what if we
18 | * have many {@code Caloric} beans? Clearly, the CDI container wouldn't know
19 | * exactly which bean we are looking for. A qualifier can be explicitly set on
20 | * the injection target (bean) and the injection point to further distinguish
21 | * between different beans.
17 | *
18 | * @SessionScoped
19 | * public class MyPreference<T> {
20 | * public T getPreference() {
21 | * // ...
22 | * }
23 | * }
24 | *
25 | *
26 | *
27 | * Which bean should these two injection points resolve to?
28 | *
29 | *
30 | *
31 | * @Inject
32 | * MyPreference<String> stringPreference;
33 | *
34 | * @Inject
35 | * MyPreference<Integer> integerPreference;
36 | *
37 | *
38 | *
39 | * Therefore, a bean must not be parameterized if it has a normal scope.
40 | * {@code @Dependent} beans however are allowed to be parameterized because they
41 | * are created one new for each injection point. The container can simply
42 | * instantiate a new raw bean of the @Dependent scope and inject that.{@code
47 | *
48 | * If the managed bean class is a generic type, it must have scope @Dependent.
49 | * If a managed bean with a parameterized bean class declares any scope other
50 | * than @Dependent, the container automatically detects the problem and treats
51 | * it as a definition error.
52 | *
53 | * }
54 | *
55 | *
56 | *
57 | * Any solution? Redesign your bean. For example:
58 | *
59 | *
60 | *
61 | * @SessionScoped
62 | * abstract class MyPreference<T> {
63 | * abstract T getPreference();
64 | * }
65 | *
66 | * public class StringPreference extends MyPreference<String> {
67 | * @Override public String getPreference() {
68 | * // ...
69 | * }
70 | * }
71 | *
72 | * public class IntegerPreference extends MyPreference<Integer> {
73 | * @Override public Integer getPreference() {
74 | * // ...
75 | * }
76 | * }
77 | *
78 | *
79 | *
80 | * ..haven't tested the code or anything. But it should do the trick.Warning
38 | *
39 | * The replaced bean is not used at all, it is "disabled". Here's the quotes on
40 | * that..{@code
44 | *
45 | * When an enabled bean, as defined in Section 5.1.2, specializes a second
46 | * bean, we can be certain that the second bean is never instantiated or
47 | * called by the container. Even if the second bean defines a producer or
48 | * observer method, the method will never be called.
49 | *
50 | * }
51 | *
52 | *
53 | * JavaDoc of {@linkplain javax.enterprise.inject.Specializes @Specializes}:
54 | * {@code
55 | *
56 | * If a bean is specialized by any enabled bean, the first bean is disabled.
57 | *
58 | * }
59 | *
60 | * However, WELD do call the observer methods of our replaced
61 | * bean, both static and non-static. So currently, the test
62 | * "stupidCalculatorObserversNotCalled" in this class fail on both GlassFish and
63 | * WildFly.
64 | *
65 | * Bug filed here.
66 | *
67 | *
68 | *
69 | * @author Martin Andersson (webmaster at martinandersson.com)
70 | */
71 | @RunWith(Arquillian.class)
72 | public class SpecializesTest
73 | {
74 | @Deployment
75 | public static WebArchive buildDeployment() {
76 | return new DeploymentBuilder(SpecializesTest.class)
77 | .addEmptyBeansXMLFile()
78 | .add(SpecializesDriver.class,
79 | StupidCalculator.class,
80 | SmartCalculator.class)
81 | .build();
82 | }
83 |
84 | static Report report; // = initialized in __callDriver().
85 |
86 | @Test
87 | @RunAsClient
88 | @InSequence(1)
89 | public void before(@ArquillianResource URL url) {
90 | report = HttpRequests.getObject(url);
91 | }
92 |
93 | @Test
94 | @RunAsClient
95 | @InSequence(2)
96 | public void injectedSpecializedCalculator() {
97 | assertEquals("Expected that @Specializes " + SmartCalculator.class.getSimpleName() + " was the injection target,",
98 | SmartCalculator.class, report.stupidCalculatorType);
99 | }
100 |
101 | @Test
102 | @InSequence(2)
103 | public void stupidCalculatorObserversNotCalled() {
104 | assertEquals("Expected that no observer methods of a \"disabled\" bean is called,",
105 | 0, StupidCalculator.OBSERVER_COUNTER.sum());
106 | }
107 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/specializes/UndeployableSpecializationTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.specializes;
2 |
3 | import com.martinandersson.javaee.cdi.specializes.lib.DefaultUserSettings;
4 | import com.martinandersson.javaee.cdi.specializes.lib.SpecializedUserSettings;
5 | import com.martinandersson.javaee.cdi.specializes.lib.UserSettings;
6 | import com.martinandersson.javaee.utils.DeploymentBuilder;
7 | import org.jboss.arquillian.container.test.api.Deployment;
8 | import org.jboss.arquillian.container.test.api.ShouldThrowException;
9 | import org.jboss.arquillian.junit.Arquillian;
10 | import org.jboss.shrinkwrap.api.spec.WebArchive;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 |
14 | /**
15 | * This undeployable deployment prove that specialization require subtyping.{@code
24 | *
25 | * If a bean class of a managed bean X is annotated @Specializes, then the
26 | * bean class of X must directly extend the bean class of another managed
27 | * bean Y. [..] If the bean class of X does not directly extend the bean
28 | * class of another managed bean, the container automatically detects the
29 | * problem and treats it as a definition error.
30 | *
31 | * }
32 | *
33 | * CDI 1.1 "4.3.1 Direct and indirect specialization":
34 | * {@code
35 | *
36 | * Furthermore, X must have all the bean types of Y. If X does not have some
37 | * bean type of Y, the container automatically detects the problem and
38 | * treats it as a definition error.
39 | *
40 | * }
41 | *
42 | *
43 | *
44 | * @author Martin Andersson (webmaster at martinandersson.com)
45 | */
46 | @RunWith(Arquillian.class)
47 | public class UndeployableSpecializationTest {
48 |
49 | @Deployment
50 | @ShouldThrowException
51 | public static WebArchive buildDeployment() {
52 | return new DeploymentBuilder(UndeployableSpecializationTest.class)
53 | .addEmptyBeansXMLFile()
54 | .add(UserSettings.class,
55 | DefaultUserSettings.class,
56 | SpecializedUserSettings.class)
57 | .build();
58 | }
59 |
60 | @Test
61 | public void specializedBeanMustExtend() {}
62 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/specializes/lib/DefaultUserSettings.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.specializes.lib;
2 |
3 | /**
4 | * @author Martin Andersson (webmaster at martinandersson.com)
5 | */
6 | public class DefaultUserSettings implements UserSettings {
7 | @Override
8 | public boolean allowMultipleDevices() {
9 | return false;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/specializes/lib/SmartCalculator.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.specializes.lib;
2 |
3 | import java.util.stream.LongStream;
4 | import javax.enterprise.inject.Specializes;
5 |
6 | /**
7 | * A better alternative to {@linkplain StupidCalculator}.
8 | *
9 | * @author Martin Andersson (webmaster at martinandersson.com)
10 | */
11 | @Specializes
12 | public class SmartCalculator extends StupidCalculator
13 | {
14 | /**
15 | * A fast summing function.
16 | *
17 | * @param values to sum
18 | * @return sum
19 | */
20 | @Override
21 | public long sum(long... values) {
22 | return LongStream.of(values).sum();
23 | }
24 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/specializes/lib/SpecializedUserSettings.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.specializes.lib;
2 |
3 | import javax.enterprise.inject.Specializes;
4 |
5 | /**
6 | * @author Martin Andersson (webmaster at martinandersson.com)
7 | */
8 | @Specializes
9 | public class SpecializedUserSettings implements UserSettings {
10 | @Override
11 | public boolean allowMultipleDevices() {
12 | return true;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/cdi/specializes/lib/StupidCalculator.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.cdi.specializes.lib;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import java.util.concurrent.atomic.LongAdder;
5 | import java.util.logging.Logger;
6 | import java.util.stream.LongStream;
7 | import javax.annotation.PostConstruct;
8 | import javax.enterprise.event.Observes;
9 |
10 | /**
11 | * Stupid calculator is a bad implementation of a calculator that some other
12 | * department of your company wrote. Now you're faced with all the
13 | * responsibility of a slow application and your customer phones in, throwing
14 | * bad words at you. Lack of internal support and lack of source code access
15 | * together with a packaging nightmare of your application makes it infeasible
16 | * to replace the class which would be the normal alternative. What to do?
11 | *
14 | *
15 | * These settings will make the singleton serialize all access to the bean.
16 | *
17 | * @author Martin Andersson (webmaster at martinandersson.com)
18 | */
19 | @javax.ejb.Singleton
20 | public class SingletonBean extends AbstractSessionBean {
21 | // All business methods provided by AbstractSessionBean
22 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/StatefulBean.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans;
2 |
3 | import javax.ejb.Remove;
4 |
5 | /**
6 | * For more info about {@code @Stateful} session beans, see JavaDoc of
7 | * {@linkplain #remove()} and a source-code comment written in the client code
8 | * of {@code TestDriver.__removeStatefulBeans()}.
9 | *
10 | * @author Martin Andersson (webmaster at martinandersson.com)
11 | */
12 | @javax.ejb.Stateful
13 | public class StatefulBean extends AbstractSessionBean
14 | {
15 | /**
16 | * JNDI lookup and @EJB injection causes a new @Stateful bean instance to be
17 | * created. The bean reference may be passed around in the system if need be
18 | * (for example put in the {@code HttpSession} object). Finally, when the
19 | * client is done using the @Stateful, he must tell the container so by
20 | * invoking a {@code @Remove} annotated method, such as this one. When this
21 | * method completes, the bean will be discarded.
9 | *
14 | *
15 | * @see EJBType
16 | * @see ExecutionSettings
17 | * @see TestDriver
18 | *
19 | * @author Martin Andersson (webmaster at martinandersson.com)
20 | */
21 | public enum Operation {
22 |
23 | /**
24 | * Makes the driver call his two injected session bean references serially
25 | * using the same thread.
26 | */
27 | CALL_TWO_SERIALLY,
28 |
29 | /**
30 | * Makes the driver call just one injected session bean reference serially
31 | * using the same thread.
32 | */
33 | CALL_ONE_SERIALLY,
34 |
35 | /**
36 | * Will make the driver call one of his session bean references concurrently
37 | * from two different threads.
38 | */
39 | CALL_ONE_CONCURRENTLY,
40 |
41 | /**
42 | * Will make the driver call one of his session bean references from within
43 | * the same bean.
44 | */
45 | SELF_INVOKING_PROXY
46 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/testdriver/Report.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans.testdriver;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | *
7 | * @author Martin Andersson (webmaster at martinandersson.com)
8 | */
9 | public class Report implements Serializable {
10 | public final int beanId1, beanId2;
11 | public final Exception exception; // JDK 8's Optional is not Serializable.
12 |
13 | public Report(int beanId1, int beanId2) {
14 | this.beanId1 = beanId1;
15 | this.beanId2 = beanId2;
16 | this.exception = null;
17 | }
18 |
19 | public Report(Exception exception) {
20 | beanId1 = beanId2 = -1;
21 | this.exception = exception;
22 | }
23 |
24 | @Override
25 | public String toString() {
26 | return new StringBuilder(Report.class.getSimpleName())
27 | .append("[")
28 | .append("beanId1=").append(beanId1)
29 | .append(", beanId2=").append(beanId2)
30 | .append(", exception=").append(exception)
31 | .append("]")
32 | .toString();
33 | }
34 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/tests/AbstractSessionTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans.tests;
2 |
3 | import com.martinandersson.javaee.ejb.sessionbeans.AbstractSessionBean;
4 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.EJBType;
5 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.ExecutionSettings;
6 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.Operation;
7 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.Report;
8 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.TestDriver;
9 | import com.martinandersson.javaee.utils.DeploymentBuilder;
10 | import com.martinandersson.javaee.utils.HttpRequests;
11 | import com.martinandersson.javaee.utils.PhasedExecutorService;
12 | import java.net.URL;
13 | import java.util.Objects;
14 | import org.jboss.arquillian.container.test.api.Deployment;
15 | import org.jboss.arquillian.junit.Arquillian;
16 | import org.jboss.arquillian.test.api.ArquillianResource;
17 | import org.jboss.shrinkwrap.api.Archive;
18 | import org.junit.runner.RunWith;
19 |
20 | /**
21 | * @author Martin Andersson (webmaster at martinandersson.com)
22 | */
23 | @RunWith(Arquillian.class)
24 | abstract class AbstractSessionTest
25 | {
26 | @Deployment
27 | private static Archive> buildDeployment() {
28 | return new DeploymentBuilder(AbstractSessionTest.class)
29 | .add(true, AbstractSessionBean.class, TestDriver.class)
30 | .add(PhasedExecutorService.class)
31 | .build();
32 | }
33 |
34 | private final EJBType type;
35 |
36 | @ArquillianResource
37 | private URL url;
38 |
39 | AbstractSessionTest(EJBType type) {
40 | this.type = Objects.requireNonNull(type);
41 | }
42 |
43 | protected final Report run(Operation operation) {
44 | ExecutionSettings settings = new ExecutionSettings(operation, type);
45 | return HttpRequests.sendGetObject(url, null, settings);
46 | }
47 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/sessionbeans/tests/SingletonTest.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.sessionbeans.tests;
2 |
3 | import com.martinandersson.javaee.ejb.sessionbeans.SingletonBean;
4 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.EJBType;
5 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.Operation;
6 | import com.martinandersson.javaee.ejb.sessionbeans.testdriver.Report;
7 | import org.jboss.arquillian.container.test.api.RunAsClient;
8 | import static org.junit.Assert.assertEquals;
9 | import org.junit.Test;
10 |
11 | /**
12 | * A {@code Singleton} is not pooled. Only one such instance exist that the
13 | * application may use.{@code
66 | *
67 | * Singleton session beans support reentrant calls, i.e., where an
68 | * outbound call from a singleton session bean method results in a
69 | * loopback call to the singleton session bean on the same thread.
70 | * Reentrant singleton session beans should be programmed and used with
71 | * caution.
72 | *
73 | * }
74 | *
75 | * EJB 3.2 specification, section "4.8.5.1.1 Reentrant Locking Behavior":
76 | * {@code
77 | *
78 | * If a loopback call occurs on a singleton session bean that already
79 | * holds a write lock on the same thread:
80 | *
81 | * If the target of the loopback call is a write method, the call
82 | * must proceed immediately, without releasing the original write
83 | * lock.
84 | *
85 | * }
86 | */
87 | @Test
88 | @RunAsClient
89 | public void loopbackToWriteLockFromWriteLockIsLegal() {
90 | Report report = run(Operation.SELF_INVOKING_PROXY);
91 |
92 | assertEquals("Only one @Singleton instance was supposed to be used, ",
93 | report.beanId1, report.beanId2);
94 | }
95 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/transactions/Base1.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.transactions;
2 |
3 | import javax.annotation.Resource;
4 | import javax.ejb.TransactionAttribute;
5 | import javax.ejb.TransactionAttributeType;
6 | import javax.transaction.TransactionSynchronizationRegistry;
7 |
8 | /**
9 | * @author Martin Andersson (webmaster at martinandersson.com)
10 | */
11 | @TransactionAttribute(TransactionAttributeType.REQUIRED)
12 | public class Base1
13 | {
14 | @Resource
15 | TransactionSynchronizationRegistry reg;
16 |
17 | @TransactionAttribute(TransactionAttributeType.NEVER)
18 | public int foo() {
19 | return reg.getTransactionStatus();
20 | }
21 | }
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/transactions/Base2.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.transactions;
2 |
3 | import javax.annotation.Resource;
4 | import javax.ejb.TransactionAttribute;
5 | import javax.ejb.TransactionAttributeType;
6 | import javax.transaction.TransactionSynchronizationRegistry;
7 |
8 | /**
9 | * @author Martin Andersson (webmaster at martinandersson.com)
10 | */
11 | @TransactionAttribute(TransactionAttributeType.SUPPORTS)
12 | public class Base2 implements NonGenericFoo
13 | {
14 | @Resource
15 | TransactionSynchronizationRegistry reg;
16 |
17 | @TransactionAttribute(TransactionAttributeType.MANDATORY)
18 | @Override
19 | public int foo() {
20 | return reg.getTransactionStatus();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/com/martinandersson/javaee/ejb/transactions/Base3.java:
--------------------------------------------------------------------------------
1 | package com.martinandersson.javaee.ejb.transactions;
2 |
3 | import javax.annotation.Resource;
4 | import javax.ejb.TransactionAttribute;
5 | import static javax.ejb.TransactionAttributeType.MANDATORY;
6 | import static javax.ejb.TransactionAttributeType.SUPPORTS;
7 | import javax.transaction.TransactionSynchronizationRegistry;
8 |
9 | /**
10 | * @author Martin Andersson (webmaster at martinandersson.com)
11 | */
12 | @TransactionAttribute(SUPPORTS)
13 | public abstract class Base3