├── .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 | *

13 | */ 14 | package com.martinandersson.javaee.arquillian.clientserver; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/helloworld/HelloWorldEJB.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.arquillian.helloworld; 2 | 3 | import java.util.logging.Logger; 4 | import javax.annotation.PostConstruct; 5 | import javax.annotation.PreDestroy; 6 | import javax.ejb.Stateless; 7 | import javax.ejb.TransactionAttribute; 8 | import javax.ejb.TransactionAttributeType; 9 | 10 | /** 11 | * This {@code @Stateless} EJB will uppercase a provided String.

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 | *

10 | */ 11 | package com.martinandersson.javaee.arquillian.helloworld; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/arquillian/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Introduces Arquillian and the project setup.

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 | *

13 | */ 14 | package com.martinandersson.javaee.arquillian.persistence; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/AlternativeDriver.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives; 2 | 3 | import com.martinandersson.javaee.cdi.alternatives.lib.UsernameService; 4 | import java.io.IOException; 5 | import java.io.ObjectOutputStream; 6 | import java.io.Serializable; 7 | import javax.inject.Inject; 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 | * Will respond to all HTTP GET request with a report that contain the class of 16 | * the bean used as the default username service. 17 | * 18 | * @author Martin Andersson (webmaster at martinandersson.com) 19 | */ 20 | @WebServlet("") 21 | public class AlternativeDriver extends HttpServlet 22 | { 23 | @Inject 24 | UsernameService usernameService; 25 | 26 | @Override 27 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 28 | Report report = new Report(usernameService.getClass()); 29 | new ObjectOutputStream(resp.getOutputStream()).writeObject(report); 30 | } 31 | 32 | static class Report> implements Serializable { 33 | final T beanType; 34 | 35 | Report(T beanType) { 36 | this.beanType = beanType; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/AlternativeTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives; 2 | 3 | import com.martinandersson.javaee.cdi.alternatives.lib.DefaultUsernameService; 4 | import com.martinandersson.javaee.cdi.alternatives.lib.PermissiveUsernameService; 5 | import com.martinandersson.javaee.cdi.alternatives.lib.UsernameService; 6 | import com.martinandersson.javaee.utils.DeploymentBuilder; 7 | import com.martinandersson.javaee.utils.HttpRequests; 8 | import java.net.URL; 9 | import org.jboss.arquillian.container.test.api.Deployment; 10 | import org.jboss.arquillian.container.test.api.RunAsClient; 11 | import org.jboss.arquillian.junit.Arquillian; 12 | import org.jboss.arquillian.test.api.ArquillianResource; 13 | import org.jboss.shrinkwrap.api.spec.WebArchive; 14 | import static org.junit.Assert.assertEquals; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | /** 19 | * Alternative beans is a way to "short-circuit" the ordinary bean resolution of 20 | * CDI and use alternatives instead of the beans the application would normally 21 | * use.

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 | *

Note 1

45 | * 46 | * It is "Common Annotations for the Java Platform 1.2", JSR-250, that 47 | * defines the @Priority annotation. CDI 1.1 is not the only Java EE 48 | * specification that use this annotation. Interceptors 1.2, JSR-318, do so to 49 | * and there might be even more that I am not aware of. 50 | * 51 | * 52 | * 53 | * @author Martin Andersson (webmaster at martinandersson.com) 54 | */ 55 | @RunWith(Arquillian.class) 56 | public class AlternativeTest 57 | { 58 | @Deployment 59 | private static WebArchive buildDeployment() { 60 | return new DeploymentBuilder(AlternativeTest.class) 61 | .addEmptyBeansXMLFile() 62 | .add(AlternativeDriver.class, 63 | UsernameService.class, 64 | DefaultUsernameService.class, 65 | PermissiveUsernameService.class) 66 | .build(); 67 | } 68 | 69 | @Test 70 | @RunAsClient 71 | public void selectedAlternativeIsUsed(@ArquillianResource URL url) { 72 | AlternativeDriver.Report report = HttpRequests.getObject(url); 73 | 74 | assertEquals("Expected that the selected alternative would be used.", 75 | PermissiveUsernameService.class, report.beanType); 76 | } 77 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/UnselectedAlternativeTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives; 2 | 3 | import com.martinandersson.javaee.cdi.alternatives.lib.DefaultUsernameService; 4 | import com.martinandersson.javaee.cdi.alternatives.lib.UnselectedUsernameService; 5 | import com.martinandersson.javaee.cdi.alternatives.lib.UsernameService; 6 | import com.martinandersson.javaee.utils.DeploymentBuilder; 7 | import com.martinandersson.javaee.utils.HttpRequests; 8 | import java.net.URL; 9 | import org.jboss.arquillian.container.test.api.Deployment; 10 | import org.jboss.arquillian.container.test.api.RunAsClient; 11 | import org.jboss.arquillian.junit.Arquillian; 12 | import org.jboss.arquillian.test.api.ArquillianResource; 13 | import org.jboss.shrinkwrap.api.spec.WebArchive; 14 | import static org.junit.Assert.assertEquals; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | /** 19 | * CDI 1.1 specification, section "2.7 Alternatives": 20 | *
{@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 } node of {@code beans.xml}. 17 | * {@linkplain com.martinandersson.javaee.cdi.alternatives.UnselectedAlternativeTest UnselectedAlternativeTest} 18 | * demonstrates that without the {@code @Priority} annotation, and without the 19 | * bean being listed in {@code beans.xml}, then the bean is safe to deploy 20 | * together with the rest of your files and will not be used, albeit this 21 | * practice would probably have no purpose. 22 | * 23 | * @author Martin Andersson (webmaster at martinandersson.com) 24 | */ 25 | @Alternative 26 | @Priority(1) // TODO: Add comment whether 1 is high or low. 27 | public class PermissiveUsernameService implements UsernameService 28 | { 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | @Override 33 | public boolean isReserved(String username) { 34 | return false; 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/lib/UnselectedUsernameService.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives.lib; 2 | 3 | import javax.enterprise.inject.Alternative; 4 | 5 | /** 6 | * Is a {@code @Alternative UsernameService}. Must be explicitly selected in the 7 | * {@code beans.xml} file or with a {@linkplain javax.annotation.Priority @Priority} 8 | * annotation. See {@linkplain PermissiveUsernameService} for an example of the 9 | * latter. 10 | * 11 | * @author Martin Andersson (webmaster at martinandersson.com) 12 | */ 13 | @Alternative 14 | public class UnselectedUsernameService implements UsernameService 15 | { 16 | @Override 17 | public boolean isReserved(String username) { 18 | throw new UnsupportedOperationException("For demonstration purposes only."); 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/alternatives/lib/UsernameService.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.alternatives.lib; 2 | 3 | /** 4 | * The username service currently only declare one method that says whether or 5 | * not a username is "reserved". A reserved username should not be accepted when 6 | * creating new users. 7 | * 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | public interface UsernameService 11 | { 12 | /** 13 | * Returns whether or not the provided username is "reserved".

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 instance; 23 | 24 | @Inject 25 | Provider provider; 26 | 27 | @Override 28 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 29 | throws ServletException, IOException 30 | { 31 | final long firstId, 32 | secondId; 33 | 34 | switch (CallFirst.valueOf(req.getParameter("callFirst"))) { 35 | case INSTANCE: 36 | firstId = instance.get().getId(); 37 | secondId = provider.get().getId(); 38 | break; 39 | case PROVIDER: 40 | firstId = provider.get().getId(); 41 | secondId = instance.get().getId(); 42 | break; 43 | default: 44 | throw new IllegalArgumentException("callFirst"); 45 | } 46 | 47 | new ObjectOutputStream(resp.getOutputStream()) 48 | .writeObject(new long[]{ firstId, secondId }); 49 | } 50 | 51 | enum CallFirst { 52 | INSTANCE, PROVIDER 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/lookup/InstanceVersusProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.lookup; 2 | 3 | import com.martinandersson.javaee.cdi.lookup.InstanceVersusProviderRunner.CallFirst; 4 | import com.martinandersson.javaee.utils.DeploymentBuilder; 5 | import com.martinandersson.javaee.utils.HttpRequests; 6 | import com.martinandersson.javaee.utils.HttpRequests.RequestParameter; 7 | import java.net.URL; 8 | import org.jboss.arquillian.container.test.api.Deployment; 9 | import org.jboss.arquillian.container.test.api.RunAsClient; 10 | import org.jboss.arquillian.junit.Arquillian; 11 | import org.jboss.arquillian.test.api.ArquillianResource; 12 | import org.jboss.shrinkwrap.api.Archive; 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertNotEquals; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | /** 19 | * The CDI specification does not say a word about the {@code Provider} 20 | * interface (defined by JSR-330). And the JavaDoc of {@code Provider} is not 21 | * very helpful either. But this test will prove that {@code Provider} return 22 | * a contextual (scoped) instance, just like {@code Instance} do.

23 | * 24 | * CDI 1.2, section "5.6.1. The Instance interface": 25 | *

{@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.

22 | * 23 | * Also see: 24 | *

{@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.

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 | *

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.

29 | * 30 | * 31 | * 32 | *

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.

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 CLOSE_EXCEPTIONS = new ArrayList<>(); 26 | 27 | 28 | @PersistenceUnit 29 | EntityManagerFactory emf; 30 | 31 | 32 | @Produces 33 | @Scope(DEPENDENT) 34 | @RequestScoped 35 | public EntityManager newEntityManager() { 36 | return emf.createEntityManager(); 37 | } 38 | 39 | public void closeEntityManager(@Disposes @Scope(DEPENDENT) EntityManager em) { 40 | try { 41 | em.close(); 42 | } 43 | catch (RuntimeException e) { 44 | CLOSE_EXCEPTIONS.add(e); 45 | } 46 | } 47 | 48 | 49 | 50 | @PostConstruct 51 | private void __incrementCounter() { 52 | INSTANCE_COUNTER.incrementAndGet(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/producer/RequestScopedEMProducer.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.producers.entitymanager.producer; 2 | 3 | import static com.martinandersson.javaee.cdi.producers.entitymanager.producer.Scope.Type.REQUEST; 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.RequestScoped; 9 | import javax.enterprise.inject.Disposes; 10 | import javax.enterprise.inject.Produces; 11 | import javax.persistence.EntityManager; 12 | import javax.persistence.EntityManagerFactory; 13 | import javax.persistence.PersistenceUnit; 14 | 15 | /** 16 | * 17 | * @author Martin Andersson (webmaster at martinandersson.com) 18 | */ 19 | @RequestScoped 20 | public class RequestScopedEMProducer 21 | { 22 | public static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); 23 | 24 | public static final List CLOSE_EXCEPTIONS = new ArrayList<>(); 25 | 26 | 27 | @PersistenceUnit 28 | EntityManagerFactory emf; 29 | 30 | 31 | 32 | @Produces 33 | @Scope(REQUEST) 34 | @RequestScoped 35 | public EntityManager newEntityManager() { 36 | return emf.createEntityManager(); 37 | } 38 | 39 | public void closeEntityManager(@Disposes @Scope(REQUEST) EntityManager em) { 40 | try { 41 | em.close(); 42 | } 43 | catch (RuntimeException e) { 44 | CLOSE_EXCEPTIONS.add(e); 45 | } 46 | } 47 | 48 | 49 | 50 | @PostConstruct 51 | private void __incrementCounter() { 52 | INSTANCE_COUNTER.incrementAndGet(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/producer/Scope.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.producers.entitymanager.producer; 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 Scope { 20 | 21 | Type value(); 22 | 23 | public enum Type { 24 | DEPENDENT, REQUEST, SESSION, APPLICATION 25 | } 26 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/producer/TestDriver.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 static com.martinandersson.javaee.cdi.producers.entitymanager.producer.Scope.Type.REQUEST; 5 | import java.io.IOException; 6 | import java.io.ObjectOutputStream; 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.EnumMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import javax.enterprise.inject.spi.CDI; 13 | import javax.enterprise.util.AnnotationLiteral; 14 | import javax.persistence.EntityManager; 15 | import javax.servlet.ServletException; 16 | import javax.servlet.annotation.WebServlet; 17 | import javax.servlet.http.HttpServlet; 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | 21 | /** 22 | * 23 | * @author Martin Andersson (webmaster at martinandersson.com) 24 | */ 25 | @WebServlet("") 26 | public class TestDriver extends HttpServlet 27 | { 28 | @Override 29 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 30 | throws ServletException, IOException 31 | { 32 | System.out.println("Dependent proof: " + lookup(DEPENDENT)); 33 | System.out.println("Request proof: " + lookup(REQUEST)); 34 | 35 | Map instances = new EnumMap<>(Scope.Type.class); 36 | instances.put(DEPENDENT, DependentEMProducer.INSTANCE_COUNTER.get()); 37 | instances.put(REQUEST, RequestScopedEMProducer.INSTANCE_COUNTER.get()); 38 | 39 | Map> exceptions = new EnumMap<>(Scope.Type.class); 40 | exceptions.put(DEPENDENT, new ArrayList<>(DependentEMProducer.CLOSE_EXCEPTIONS)); 41 | exceptions.put(REQUEST, new ArrayList<>(RequestScopedEMProducer.CLOSE_EXCEPTIONS)); 42 | 43 | new ObjectOutputStream(resp.getOutputStream()) 44 | .writeObject(new Report(instances, exceptions)); 45 | } 46 | 47 | private EntityManager lookup(Scope.Type type) { 48 | return CDI.current() 49 | .select(EntityManager.class, ScopeAnnotation.of(type)) 50 | .get(); 51 | } 52 | 53 | static class Report implements Serializable { 54 | final Map instancesCreated; 55 | final Map> closeExceptions; 56 | 57 | Report(Map instancesCreated, 58 | Map> closeExceptions) 59 | { 60 | this.instancesCreated = instancesCreated; 61 | this.closeExceptions = closeExceptions; 62 | } 63 | } 64 | 65 | static abstract class ScopeAnnotation extends AnnotationLiteral implements Scope { 66 | public static AnnotationLiteral of(Scope.Type type) { 67 | return new ScopeAnnotation() { 68 | @Override public Type value() { 69 | return type; 70 | } 71 | }; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/unsafe/EntityManagerConsumer.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.producers.entitymanager.unsafe; 2 | 3 | import javax.annotation.PostConstruct; 4 | import javax.enterprise.context.ApplicationScoped; 5 | import javax.inject.Inject; 6 | import javax.persistence.EntityManager; 7 | 8 | /** 9 | * 10 | * @author Martin Andersson (webmaster at martinandersson.com) 11 | */ 12 | @ApplicationScoped 13 | public class EntityManagerConsumer 14 | { 15 | @Inject 16 | @UserDatabase 17 | EntityManager em; 18 | 19 | 20 | @PostConstruct 21 | private void __logEntityManager() { 22 | OracleTestDriver.log("CONSUMER-POSTCONSTRUCT CDI PROXY", em); 23 | } 24 | 25 | 26 | public EntityManager getEntityManager() { 27 | return em; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/producers/entitymanager/unsafe/OracleProducer.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.producers.entitymanager.unsafe; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | import java.util.logging.Logger; 5 | import javax.annotation.PostConstruct; 6 | import javax.enterprise.inject.Disposes; 7 | import javax.enterprise.inject.Produces; 8 | import javax.inject.Singleton; 9 | import javax.persistence.EntityManager; 10 | import javax.persistence.PersistenceContext; 11 | 12 | /** 13 | * This class is [almost] a replica of: 14 | * 15 | *

{@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.

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> report = new Report( 50 | water.getClass(), broccoli.getClass(), meat.getClass()); 51 | 52 | ObjectOutputStream out = new ObjectOutputStream(resp.getOutputStream()); 53 | out.writeObject(report); 54 | } 55 | 56 | 57 | 58 | static class Report> implements Serializable { 59 | final T defaultType, healthyType, unhealthyType; 60 | 61 | Report(T defaultType, T healthyType, T unhealthyType) { 62 | this.defaultType = defaultType; 63 | this.healthyType = healthyType; 64 | this.unhealthyType = unhealthyType; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/qualifiers/QualifierTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.qualifiers; 2 | 3 | import com.martinandersson.javaee.cdi.qualifiers.QualifierDriver.Report; 4 | import com.martinandersson.javaee.cdi.qualifiers.lib.Broccoli; 5 | import com.martinandersson.javaee.cdi.qualifiers.lib.Caloric; 6 | import com.martinandersson.javaee.cdi.qualifiers.lib.Healthy; 7 | import com.martinandersson.javaee.cdi.qualifiers.lib.Meat; 8 | import com.martinandersson.javaee.cdi.qualifiers.lib.Unhealthy; 9 | import com.martinandersson.javaee.cdi.qualifiers.lib.Water; 10 | import com.martinandersson.javaee.utils.DeploymentBuilder; 11 | import com.martinandersson.javaee.utils.HttpRequests; 12 | import java.net.URL; 13 | import org.jboss.arquillian.container.test.api.Deployment; 14 | import org.jboss.arquillian.container.test.api.RunAsClient; 15 | import org.jboss.arquillian.junit.Arquillian; 16 | import org.jboss.arquillian.test.api.ArquillianResource; 17 | import org.jboss.shrinkwrap.api.spec.WebArchive; 18 | import static org.junit.Assert.assertEquals; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | 22 | /** 23 | * TODO: Write something. 24 | * 25 | * @author Martin Andersson (webmaster at martinandersson.com) 26 | */ 27 | @RunWith(Arquillian.class) 28 | public class QualifierTest 29 | { 30 | @Deployment 31 | public static WebArchive buildDeployment() { 32 | return new DeploymentBuilder(QualifierTest.class) 33 | .addEmptyBeansXMLFile() 34 | .add( 35 | // Driver 36 | QualifierDriver.class, 37 | 38 | // Qualifiers 39 | Healthy.class, 40 | Unhealthy.class, 41 | 42 | // Common bean type 43 | Caloric.class, 44 | 45 | // Caloric implementations 46 | Water.class, 47 | Broccoli.class, 48 | Meat.class) 49 | .build(); 50 | } 51 | 52 | @Test 53 | @RunAsClient 54 | public void qualifierTest(@ArquillianResource URL url) { 55 | Report> report = HttpRequests.getObject(url); 56 | 57 | assertEquals("Expected that Water is the only @Default Caloric bean.", 58 | Water.class, report.defaultType); 59 | 60 | assertEquals("Expected that Broccoli is the only @Healthy Caloric bean.", 61 | Broccoli.class, report.healthyType); 62 | 63 | assertEquals("Expected that Meat is the only @Unhealthy Caloric bean.", 64 | Meat.class, report.unhealthyType); 65 | } 66 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/qualifiers/lib/Broccoli.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.qualifiers.lib; 2 | 3 | /** 4 | * Broccoli is healthy for you!

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 | *

 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.

43 | * 44 | * Source (CDI 1.1 specification, section "3.1 Managed Beans"): 45 | * 46 | *

{@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.

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 { 110 | T repeat(T any) { 111 | return any; 112 | } 113 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/resolution/SimpleCalculator.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.resolution; 2 | 3 | import java.util.stream.LongStream; 4 | 5 | /** 6 | * The {@code SimpleCalculator} does not implement an interface. 7 | * 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | class SimpleCalculator { 11 | public long sum(long... values) { 12 | return LongStream.of(values).sum(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/scope/application/ApplicationScopedConcurrencyTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.scope.application; 2 | 3 | import com.martinandersson.javaee.utils.DeploymentBuilder; 4 | import com.martinandersson.javaee.utils.PhasedExecutorService; 5 | import javax.annotation.Resource; 6 | import javax.enterprise.concurrent.ManagedThreadFactory; 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 | * Unlike the EJB specification, the CDI specification doesn't say a thing about 17 | * multithreading semantics of the CDI singleton.

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 getIdOfNestedRequestedScopedBeanAsynchronously() { 30 | return new AsyncResult<>(bean.getId()); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/scope/request/TestDriver1.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.scope.request; 2 | 3 | import java.io.IOException; 4 | import java.io.ObjectOutputStream; 5 | import java.io.Serializable; 6 | import java.util.concurrent.TimeUnit; 7 | import javax.ejb.EJB; 8 | import javax.inject.Inject; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.annotation.WebServlet; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * @author Martin Andersson (webmaster at martinandersson.com) 17 | */ 18 | @WebServlet("/TestDriver1") 19 | public class TestDriver1 extends HttpServlet { 20 | 21 | @Inject 22 | RequestScopedBean requestScopedBean; 23 | 24 | @Inject 25 | ApplicationScopedBean applicationScopedBean; 26 | 27 | @EJB 28 | StatelessBean statelessBean; 29 | 30 | @Override 31 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 32 | 33 | // Used by concurrent tests: 34 | if ("true".equalsIgnoreCase(req.getParameter("sleep"))) { 35 | try { 36 | TimeUnit.SECONDS.sleep(1); 37 | } catch (InterruptedException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | Report report = new Report( 43 | requestScopedBean.getId(), 44 | requestScopedBean.getIdOfNestedRequestedScopedBean(), 45 | applicationScopedBean.getIdOfNestedRequestedScopedBean(), 46 | statelessBean.getIdOfNestedRequestedScopedBean()); 47 | 48 | ObjectOutputStream out = new ObjectOutputStream(resp.getOutputStream()); 49 | out.writeObject(report); 50 | } 51 | 52 | public static final class Report implements Serializable { 53 | 54 | final int servletInjectedRequestScopedId, 55 | selfNestedRequestScopedId, 56 | singletonOwnedRequestScopedId, 57 | statelessOwnedRequestScopedId; 58 | 59 | Report(int servletInjectedRequestScopedId, 60 | int selfNestedRequestScopedId, 61 | int singletonOwnedRequestScopedId, 62 | int statelessOwnedRequestScopedId) 63 | { 64 | this.servletInjectedRequestScopedId = servletInjectedRequestScopedId; 65 | this.selfNestedRequestScopedId = selfNestedRequestScopedId; 66 | this.singletonOwnedRequestScopedId = singletonOwnedRequestScopedId; 67 | this.statelessOwnedRequestScopedId = statelessOwnedRequestScopedId; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/scope/request/TestDriver2.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.scope.request; 2 | 3 | import java.io.IOException; 4 | import java.io.ObjectOutputStream; 5 | import java.io.Serializable; 6 | import java.util.concurrent.ExecutionException; 7 | import javax.ejb.EJB; 8 | import javax.inject.Inject; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.annotation.WebServlet; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * @author Martin Andersson (webmaster at martinandersson.com) 17 | */ 18 | @WebServlet("/TestDriver2") 19 | public class TestDriver2 extends HttpServlet { 20 | 21 | @Inject 22 | RequestScopedBean requestScopedBean; 23 | 24 | @EJB 25 | StatelessBean statelessBean; 26 | 27 | @Override 28 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 29 | 30 | final int $this = requestScopedBean.getId(); 31 | 32 | final int nested; 33 | 34 | try { 35 | nested = statelessBean.getIdOfNestedRequestedScopedBeanAsynchronously().get(); 36 | } 37 | catch (InterruptedException | ExecutionException e) { 38 | throw new RuntimeException(e); 39 | } 40 | 41 | Report report = new Report($this, nested); 42 | 43 | ObjectOutputStream out = new ObjectOutputStream(resp.getOutputStream()); 44 | out.writeObject(report); 45 | } 46 | 47 | public static final class Report implements Serializable { 48 | 49 | final int servletInjectedRequestScopedId, 50 | statelessOwnedRequestScopedId; 51 | 52 | Report(int servletInjectedRequestScopedId, 53 | int statelessOwnedRequestScopedId) 54 | { 55 | this.servletInjectedRequestScopedId = servletInjectedRequestScopedId; 56 | this.statelessOwnedRequestScopedId = statelessOwnedRequestScopedId; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/specializes/SpecializesDriver.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.specializes; 2 | 3 | import com.martinandersson.javaee.cdi.specializes.lib.StupidCalculator; 4 | import java.io.IOException; 5 | import java.io.ObjectOutputStream; 6 | import java.io.Serializable; 7 | import javax.enterprise.event.Event; 8 | import javax.inject.Inject; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.annotation.WebServlet; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * @see SpecializesTest 17 | * @see StupidCalculator 18 | * @see com.martinandersson.javaee.cdi.specializes.lib.SmartCalculator SmartCalculator 19 | * 20 | * @author Martin Andersson (webmaster at martinandersson.com) 21 | */ 22 | @WebServlet("") 23 | public class SpecializesDriver extends HttpServlet { 24 | 25 | @Inject 26 | StupidCalculator stupidCalculator; 27 | 28 | @Inject 29 | Event stringEvents; 30 | 31 | 32 | 33 | @Override 34 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 35 | stringEvents.fire("Hello World"); 36 | Report report = new Report(stupidCalculator.getClass()); 37 | new ObjectOutputStream(resp.getOutputStream()).writeObject(report); 38 | } 39 | 40 | 41 | 42 | static class Report > implements Serializable { 43 | 44 | final T stupidCalculatorType; 45 | 46 | Report(T stupidCalculatorType) { 47 | this.stupidCalculatorType = stupidCalculatorType; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/cdi/specializes/SpecializesTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.cdi.specializes; 2 | 3 | import com.martinandersson.javaee.cdi.specializes.SpecializesDriver.Report; 4 | import com.martinandersson.javaee.cdi.specializes.lib.SmartCalculator; 5 | import com.martinandersson.javaee.cdi.specializes.lib.StupidCalculator; 6 | import com.martinandersson.javaee.utils.DeploymentBuilder; 7 | import com.martinandersson.javaee.utils.HttpRequests; 8 | import java.net.URL; 9 | import org.jboss.arquillian.container.test.api.Deployment; 10 | import org.jboss.arquillian.container.test.api.RunAsClient; 11 | import org.jboss.arquillian.junit.Arquillian; 12 | import org.jboss.arquillian.junit.InSequence; 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 | * Specialization in Java SE programming is realized using inheritance. The 21 | * subclass is a specialized version of the more generic superclass. So too can 22 | * a CDI bean specialize another bean. For the CDI container to always select 23 | * the specialized bean as the injection target, the bean class must be 24 | * annotated with 25 | * {@linkplain javax.enterprise.inject.Specializes @Specializes}.

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 | *

Warning

38 | * 39 | * The replaced bean is not used at all, it is "disabled". Here's the quotes on 40 | * that..

41 | * 42 | * CDI 1.1 specification, section "4.3 Specialization": 43 | *

{@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.

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 | *

{@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?

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 | *

    11 | *
  1. {@code @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)}
  2. 12 | *
  3. {@code @Lock(LockType.WRITE)}
  4. 13 | *
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.

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 | * @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.

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
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 implements GenericFoo1 14 | { 15 | @Resource 16 | TransactionSynchronizationRegistry reg; 17 | 18 | @TransactionAttribute(MANDATORY) 19 | @Override 20 | public int foo(T ignored) { 21 | return reg.getTransactionStatus(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Base4.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.martinandersson.javaee.ejb.transactions; 7 | 8 | import javax.annotation.Resource; 9 | import javax.ejb.TransactionAttribute; 10 | import static javax.ejb.TransactionAttributeType.NEVER; 11 | import javax.transaction.TransactionSynchronizationRegistry; 12 | 13 | /** 14 | * 15 | * @author Martin Andersson (webmaster at martinandersson.com) 16 | */ 17 | @TransactionAttribute(NEVER) 18 | public abstract class Base4 implements GenericFoo1 19 | { 20 | @Resource 21 | TransactionSynchronizationRegistry reg; 22 | 23 | @TransactionAttribute(NEVER) 24 | @Override 25 | public int foo(T ignored) { 26 | return reg.getTransactionStatus(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Base5.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 Base5 implements GenericFoo2 14 | { 15 | @Resource 16 | TransactionSynchronizationRegistry reg; 17 | 18 | @TransactionAttribute(MANDATORY) 19 | @Override 20 | public int foo(T ignored) { 21 | return reg.getTransactionStatus(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Base7.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 Base7 13 | { 14 | @Resource 15 | TransactionSynchronizationRegistry reg; 16 | 17 | @TransactionAttribute(TransactionAttributeType.MANDATORY) 18 | public int foo(T ignored) { 19 | return reg.getTransactionStatus(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived1.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | 5 | /** 6 | * @author Martin Andersson (webmaster at martinandersson.com) 7 | */ 8 | @Stateless 9 | public class Derived1 extends Base1 10 | { 11 | @Override 12 | public int foo() { 13 | return super.foo(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived2.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | import javax.ejb.TransactionAttribute; 5 | import javax.ejb.TransactionAttributeType; 6 | 7 | /** 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | @Stateless 11 | @TransactionAttribute(TransactionAttributeType.SUPPORTS) 12 | public class Derived2 extends Base2 13 | { 14 | @TransactionAttribute(TransactionAttributeType.REQUIRED) 15 | @Override 16 | public int foo() { 17 | return super.foo(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived3.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | import javax.ejb.TransactionAttribute; 5 | import static javax.ejb.TransactionAttributeType.REQUIRED; 6 | import static javax.ejb.TransactionAttributeType.SUPPORTS; 7 | 8 | /** 9 | * @author Martin Andersson (webmaster at martinandersson.com) 10 | */ 11 | @Stateless 12 | @TransactionAttribute(SUPPORTS) 13 | public class Derived3 extends Base3 14 | { 15 | @TransactionAttribute(REQUIRED) 16 | @Override 17 | public int foo(Integer ignored) { 18 | return super.foo(123); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived4.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | import javax.ejb.TransactionAttribute; 5 | import static javax.ejb.TransactionAttributeType.REQUIRED; 6 | import static javax.ejb.TransactionAttributeType.SUPPORTS; 7 | 8 | /** 9 | * @author Martin Andersson (webmaster at martinandersson.com) 10 | */ 11 | @Stateless 12 | @TransactionAttribute(SUPPORTS) 13 | public class Derived4 extends Base4 14 | { 15 | @TransactionAttribute(REQUIRED) 16 | @Override 17 | public int foo(Integer ignored) { 18 | return super.foo(123); 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived5.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | import javax.ejb.TransactionAttribute; 5 | import static javax.ejb.TransactionAttributeType.REQUIRED; 6 | import static javax.ejb.TransactionAttributeType.SUPPORTS; 7 | 8 | /** 9 | * @author Martin Andersson (webmaster at martinandersson.com) 10 | */ 11 | @Stateless 12 | @TransactionAttribute(SUPPORTS) 13 | public class Derived5 extends Base5 14 | { 15 | @TransactionAttribute(REQUIRED) 16 | @Override 17 | public int foo(T ignored) { 18 | return super.foo(ignored); 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived6.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | import javax.ejb.TransactionAttribute; 5 | import static javax.ejb.TransactionAttributeType.REQUIRED; 6 | import static javax.ejb.TransactionAttributeType.SUPPORTS; 7 | 8 | /** 9 | * @author Martin Andersson (webmaster at martinandersson.com) 10 | */ 11 | @Stateless 12 | @TransactionAttribute(SUPPORTS) 13 | public class Derived6 extends Base4 14 | { 15 | @TransactionAttribute(REQUIRED) 16 | @Override 17 | public int foo(Object ignored) { 18 | return super.foo(new Object()); 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/Derived7.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | import javax.ejb.Stateless; 4 | import javax.ejb.TransactionAttribute; 5 | import javax.ejb.TransactionAttributeType; 6 | 7 | /** 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | @Stateless 11 | @TransactionAttribute(TransactionAttributeType.SUPPORTS) 12 | public class Derived7 extends Base7 13 | { 14 | @TransactionAttribute(TransactionAttributeType.REQUIRED) 15 | @Override 16 | public int foo(Integer ignored) { 17 | return super.foo(123); 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/GenericFoo1.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | /** 4 | * @author Martin Andersson (webmaster at martinandersson.com) 5 | */ 6 | public interface GenericFoo1 { 7 | public int foo(T ignored); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/GenericFoo2.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.ejb.transactions; 2 | 3 | /** 4 | * @author Martin Andersson (webmaster at martinandersson.com) 5 | */ 6 | public interface GenericFoo2 { 7 | public int foo(T ignored); 8 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/NonGenericFoo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.martinandersson.javaee.ejb.transactions; 7 | 8 | /** 9 | * 10 | * @author Martin Andersson (webmaster at martinandersson.com) 11 | */ 12 | public interface NonGenericFoo 13 | { 14 | public int foo(); 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/ejb/transactions/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * EJB 3.2, section "8.4 Application Assembler's Responsibilities": 3 | *
{@code
 4 |  *     [..] the behavior specified by the transaction attributes is typically an
 5 |  *     intrinsic part of the semantics of an application.
 6 |  * }
7 | * 8 | * TODO: "Inheritance" of bean managed transactions into beans who use container 9 | * managed transaction demarcation. Beans that use bean managed transaction 10 | * demarcation does not inherit CMT and will most likely fail if trying to start 11 | * a nested transaction. 12 | */ 13 | package com.martinandersson.javaee.ejb.transactions; 14 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/AbstractJTAEntityManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers; 2 | 3 | import javax.ejb.EJBException; 4 | import javax.persistence.EntityManager; 5 | import org.junit.Test; 6 | 7 | /** 8 | * This class has all tests that is applicable to entity managers that use JTA 9 | * transactions, whether they be container-managed or application-managed. 10 | * 11 | * @author Martin Andersson (webmaster at martinandersson.com) 12 | */ 13 | public abstract class AbstractJTAEntityManagerTest 14 | { 15 | /** 16 | * Returns the target bean that offer an exposed {@code EntityManager}.

17 | * 18 | * One must not assume anything about the state of the entity manager's 19 | * persistence context after this method has been called. 20 | * 21 | * @return the target bean that offer an exposed {@code EntityManager} 22 | */ 23 | protected abstract EntityManagerExposer getBeanBeingTested(); 24 | 25 | /** 26 | * Entity manager is a "JTA entity manager". Use of {@code 27 | * EntityManager.getTransaction()} is forbidden. 28 | * 29 | * @throws Throwable hopefully 30 | */ 31 | @Test(expected = IllegalStateException.class) 32 | public void JTAEntityManagerMustNotUseResourceLocalTransaction() throws Throwable { 33 | try { 34 | getBeanBeingTested().accept(EntityManager::getTransaction); 35 | } 36 | catch (EJBException e) { 37 | throw e.getCause(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/EntityManagerExposer.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Function; 5 | import javax.persistence.EntityManager; 6 | 7 | /** 8 | * A bean that implements this interface has an entity manager ready to be 9 | * exposed for some serious testing =)

10 | * 11 | * Yes, I originally called this type {@code EntityManagerPimp}.

12 | * 13 | * 14 | * 15 | *

Is this interface used as an EJB business interface?

16 | * 17 | * By default in Java EE, an EJB that implements an interface (except 18 | * {@code Serializable} and something more?) will loose his no-interface view 19 | * and client code will become unable to declare and use a dependency of the EJB 20 | * bean type alone.

21 | * 22 | * The implemented interface is being looked at as a "business interface" (a 23 | * {@code @Local} one to be more precise) and it is the interface type that must 24 | * be used as the type of the injection point.

25 | * 26 | * If the bean whish to implement this interface and keep exposing a 27 | * no-interface view to client code, then the bean must be annotated 28 | * {@code @LocalBean}. 29 | * 30 | * @author Martin Andersson (webmaster at martinandersson.com) 31 | */ 32 | public interface EntityManagerExposer 33 | { 34 | /** 35 | * Will execute the provided test logic on the inside of the bean using the 36 | * bean's entity manager.

37 | * 38 | * Default implementation uses {@link #apply(Function)}. 39 | * 40 | * @param entityManagerConsumer logic to be applied using the bean's entity 41 | * manager 42 | */ 43 | default void accept(Consumer entityManagerConsumer) { 44 | apply(em -> { entityManagerConsumer.accept(em); return null; }); 45 | } 46 | 47 | /** 48 | * Will execute the provided test logic on the inside of the bean using the 49 | * bean's entity manager. 50 | * 51 | * @param type of return value 52 | * @param entityManagerFunction logic to be applied using the bean's entity 53 | * manager 54 | * 55 | * @return whatever the function return 56 | * 57 | */ 58 | R apply(Function entityManagerFunction); 59 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/applicationmanaged/ResourceLocalTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.applicationmanaged; 2 | 3 | /** 4 | *

NOT FINISHED

5 | * 6 | * All tests today inject a persistence unit or context without specifying the 7 | * unit's name as defined in the persistence.xml file. That is okay, as long as 8 | * there is just one unit in there. Adding another, resource-local, persistence 9 | * unit to the file would require a explicit unit name by all injection points. 10 | * Wouldn't want that!

11 | * 12 | * So it would be preferable to avoid using real persistence.xml files and 13 | * instead build them during deployment. User can always open the deployed 14 | * archive that is left behind in the deployments folder if he want to examine 15 | * the contents.

16 | * 17 | * Sooo to even begin writing this test class, the API to deploy a persistence 18 | * archive must be reworked to offer a builder that accept all configuration 19 | * parameters. One being whether or not the persistence unit should be JTA or 20 | * resource-local. Leaving that for the future! =)

21 | * 22 | * 23 | *

To Test

24 | * 25 | * JPA 2.1, section "7.5.2 Resource-local EntityManagers": 26 | *
{@code
27 |  * 
28 |  *     Resource-local entity managers may use server or local resources to
29 |  *     connect to the database and are unaware of the presence of JTA
30 |  *     transactions that may or may not be active.
31 |  * 
32 |  * }
33 | * 34 | * Okay sure. However, JTA 1.2, section "3.4.7 Local and Global Transactions" 35 | * say: 36 | *
{@code
37 |  * 
38 |  *    If a resource adapter does not support mixing local and global
39 |  *    transactions within the same connection, the resource adapter should
40 |  *    throw the resource specific exception. For example, java.sql.SQLException
41 |  *    is thrown to the application if the resource manager for the underlying
42 |  *    RDBMS does not support mixing local and global transactions within the
43 |  *    same JDBC connection.
44 |  * 
45 |  * }
46 | * 47 | * If I understood the first quote correctly, then a resource-local EM can be 48 | * used with JTA as well?? Yet JTA says that's a big no no. So examine, who's 49 | * rite. 50 | * 51 | * 52 | * 53 | * @author Martin Andersson (webmaster at martinandersson.com) 54 | */ 55 | //@RunWith(Arquillian.class) 56 | public class ResourceLocalTest 57 | { 58 | 59 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/containermanaged/AbstractContainerManagedEntityManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.containermanaged; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.AbstractJTAEntityManagerTest; 4 | import javax.ejb.EJBException; 5 | import javax.persistence.EntityManager; 6 | import org.jboss.arquillian.junit.Arquillian; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | /** 11 | * This class has all tests that is applicable to container-managed entity 12 | * managers, independent of the persistence context's type of synchronization or 13 | * type of scope.

14 | * 15 | * A container-managed entity manager uses JTA transactions. Therefore, this 16 | * class extends {@linkplain AbstractJTAEntityManagerTest}.

17 | * 18 | * JPA 2.1, section "7.5 Controlling Transactions": 19 | *

{@code
20 |  * 
21 |  *     A container-managed entity manager must be a JTA entity manager.
22 |  * 
23 |  * }
24 | * 25 | * @author Martin Andersson (webmaster at martinandersson.com) 26 | */ 27 | @RunWith(Arquillian.class) 28 | public abstract class AbstractContainerManagedEntityManagerTest extends AbstractJTAEntityManagerTest 29 | { 30 | /** 31 | * JPA 2.1, section "7.9.1 Container Responsibilities": 32 | *
{@code
33 |      * 
34 |      *     The container must throw the IllegalStateException if the application
35 |      *     calls EntityManager.close on a container-managed entity manager.
36 |      * 
37 |      * }
38 | * 39 | * Makes sense. The container is the manager of the entity manager's life 40 | * cycle!

41 | * 42 | * Note that it makes no difference if the persistence context has been 43 | * initialized or not. 44 | * 45 | * @throws Throwable hopefully 46 | */ 47 | @Test(expected = IllegalStateException.class) 48 | public void containerManagedEntityManagerCanNotBeClosedByApplication() throws Throwable { 49 | try { 50 | getBeanBeingTested().accept(EntityManager::close); 51 | } 52 | catch (EJBException e) { 53 | throw e.getCause(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/BeanManagedTx.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Function; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | import javax.annotation.Resource; 8 | import javax.ejb.Stateless; 9 | import javax.ejb.TransactionManagement; 10 | import javax.ejb.TransactionManagementType; 11 | import javax.persistence.EntityManager; 12 | import javax.persistence.PersistenceContext; 13 | import javax.transaction.HeuristicMixedException; 14 | import javax.transaction.HeuristicRollbackException; 15 | import javax.transaction.NotSupportedException; 16 | import javax.transaction.RollbackException; 17 | import javax.transaction.SystemException; 18 | import javax.transaction.UserTransaction; 19 | 20 | /** 21 | * A {@code @Stateless} bean that uses bean-managed transactions and a 22 | * persistence context of default type {@code 23 | * PersistenceContextType.TRANSACTION}.

24 | * 25 | * @author Martin Andersson (webmaster at martinandersson.com) 26 | */ 27 | @Stateless 28 | @TransactionManagement(TransactionManagementType.BEAN) 29 | public class BeanManagedTx 30 | { 31 | private static final Logger LOGGER = Logger.getLogger(BeanManagedTx.class.getName()); 32 | 33 | @PersistenceContext 34 | EntityManager em; 35 | 36 | @Resource 37 | UserTransaction tx; 38 | 39 | public R applyWithTransaction(Function function) throws 40 | // thrown by tx.begin(): 41 | NotSupportedException, SystemException, 42 | // thrown by tx.commit(): 43 | RollbackException, HeuristicMixedException, HeuristicRollbackException 44 | { 45 | 46 | tx.begin(); 47 | 48 | final R result; 49 | 50 | try { 51 | result = function.apply(em); 52 | } 53 | catch (Exception e1) { 54 | try { 55 | tx.rollback(); 56 | } 57 | catch (IllegalStateException | SecurityException | SystemException e2) { 58 | LOGGER.log(Level.WARNING, "Caught and consumed an exception while trying to rollback transaction:", e2); 59 | } 60 | throw e1; 61 | } 62 | 63 | tx.commit(); 64 | 65 | return result; 66 | } 67 | 68 | public void acceptWithoutTransaction(Consumer consumer) { 69 | consumer.accept(em); 70 | } 71 | 72 | public R applyWithoutTransaction(Function function) { 73 | return function.apply(em); 74 | } 75 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/ContainerManagedTx.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.ejb.LocalBean; 6 | import javax.ejb.Stateless; 7 | import javax.ejb.TransactionAttribute; 8 | import javax.ejb.TransactionAttributeType; 9 | import javax.persistence.EntityManager; 10 | import javax.persistence.PersistenceContext; 11 | 12 | /** 13 | * A {@code @Stateless} bean that uses default container-managed transactions 14 | * and a persistence context of default type {@code 15 | * PersistenceContextType.TRANSACTION}.

16 | * 17 | * This bean represents the most "default" setup one can get =) 18 | * 19 | * @author Martin Andersson (webmaster at martinandersson.com) 20 | */ 21 | @Stateless 22 | @LocalBean 23 | public class ContainerManagedTx implements EntityManagerExposer 24 | { 25 | @PersistenceContext 26 | EntityManager em; 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | @Override 32 | public R apply(Function entityManagerFunction) { 33 | return entityManagerFunction.apply(em); 34 | } 35 | 36 | /** 37 | * Equivalent to {@linkplain #apply(Function)}, difference being that this 38 | * method will not use a transaction and possibly suspend a transaction if 39 | * one is active when this method is invoked. 40 | */ 41 | @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 42 | public R applyWithoutTransaction(Function entityManagerFunction) { 43 | return entityManagerFunction.apply(em); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/Product.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import java.util.Objects; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.Id; 7 | import javax.persistence.Table; 8 | 9 | /** 10 | * A dumb {@code @Entity} that represents a product.

11 | * 12 | * Has only one field of interest: a name. 13 | * 14 | * @author Martin Andersson (webmaster at martinandersson.com) 15 | */ 16 | @Entity 17 | @Table(schema = "JPA_ENTITYMANAGERS") 18 | public class Product 19 | { 20 | public interface Fields { 21 | String NAME = "name"; 22 | } 23 | 24 | @Id 25 | @GeneratedValue 26 | private long id; 27 | 28 | private String name; 29 | 30 | protected Product() { 31 | // Empty 32 | } 33 | 34 | public Product(String name) { 35 | setName(name); 36 | } 37 | 38 | public long getId() { 39 | return id; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return Long.hashCode(id); 53 | } 54 | 55 | @Override 56 | public boolean equals(Object obj) { 57 | if (this == obj) { 58 | return true; 59 | } 60 | 61 | if (obj == null) { 62 | return false; 63 | } 64 | 65 | // Symmetry? Yes please. 66 | if (this.getClass() != obj.getClass()) { 67 | return false; 68 | } 69 | 70 | Product that = (Product) obj; 71 | 72 | return this.id == that.id && 73 | // I would normally be happy with just the id, at least one test need name equality though.. 74 | Objects.equals(this.name, that.name); 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return new StringBuilder(Product.class.getSimpleName()) 80 | .append("[") 81 | .append("id=").append(id) 82 | .append(", name=").append(name) 83 | .append("]") 84 | .toString(); 85 | } 86 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/Products.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import java.util.List; 4 | import java.util.function.Consumer; 5 | import java.util.function.Function; 6 | import javax.persistence.EntityManager; 7 | import javax.persistence.NoResultException; 8 | import javax.persistence.criteria.CriteriaBuilder; 9 | import javax.persistence.criteria.CriteriaQuery; 10 | import javax.persistence.criteria.Path; 11 | import javax.persistence.criteria.Root; 12 | 13 | /** 14 | * Utility class with functional factory methods for all CRUD-like operations 15 | * expected from a {@code Product} repository.

16 | * 17 | * The produced functions are normally used as arguments to a {@code 18 | * EntityManagerExposer}. 19 | * 20 | * @author Martin Andersson (webmaster at martinandersson.com) 21 | */ 22 | public final class Products 23 | { 24 | private Products() { 25 | // Empty 26 | } 27 | 28 | public static Function create(String productName) { 29 | return em -> { 30 | Product p = new Product(productName); 31 | em.persist(p); 32 | return p; 33 | }; 34 | } 35 | 36 | public static Function> findAll() { 37 | return em -> { 38 | CriteriaBuilder b = em.getCriteriaBuilder(); 39 | CriteriaQuery q = b.createQuery(Product.class); 40 | 41 | Path root = q.from(Product.class); 42 | 43 | /* 44 | * There is no default SELECT clause in the spec (section 6.5.11). 45 | * EclipseLink default to selecting the FROM clause, or "root". Not 46 | * that sure what the hell Hibernate does. Best practice is to 47 | * always include this thing: 48 | * 49 | * TODO: Examine. 50 | */ 51 | q.select(root); 52 | 53 | return em.createQuery(q).getResultList(); 54 | }; 55 | } 56 | 57 | public static Function findById(long id) { 58 | return em -> { 59 | return em.find(Product.class, id); 60 | }; 61 | } 62 | 63 | public static Function findByIdOf(Product product) { 64 | long id = product.getId(); 65 | 66 | if (id <= 0) { 67 | throw new IllegalArgumentException("Product has no id."); 68 | } 69 | 70 | return findById(id); 71 | } 72 | 73 | /** 74 | * Returns a function that will search for one single product of the 75 | * provided name, returning {@code null} if none was found. 76 | * 77 | * @param productName name of product 78 | * 79 | * @return a function that will search for one single product of the 80 | * provided name, returning {@code null} if none was found 81 | */ 82 | public static Function findByUniqueName(String productName) { 83 | return em -> { 84 | CriteriaBuilder b = em.getCriteriaBuilder(); 85 | CriteriaQuery q = b.createQuery(Product.class); 86 | 87 | Root from = q.from(Product.class); 88 | q.where(b.equal(from.get(Product.Fields.NAME), productName)); 89 | q.select(from); 90 | 91 | try { 92 | return em.createQuery(q).getSingleResult(); 93 | } 94 | catch (NoResultException e) { 95 | return null; 96 | } 97 | }; 98 | } 99 | 100 | public static Consumer remove(Product product) { 101 | return em -> em.remove(product); 102 | } 103 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/SingletonWithExtended.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.ejb.LocalBean; 6 | import javax.ejb.Singleton; 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | import javax.persistence.PersistenceContextType; 10 | 11 | 12 | /** 13 | * This bean is a {@code @Singleton} EJB that try to use an entity manager of 14 | * type {@code PersistenceContextType.EXTENDED}. 15 | * 16 | * @author Martin Andersson (webmaster at martinandersson.com) 17 | */ 18 | @Singleton 19 | @LocalBean 20 | public class SingletonWithExtended implements EntityManagerExposer 21 | { 22 | @PersistenceContext(type = PersistenceContextType.EXTENDED) 23 | EntityManager em; 24 | 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | @Override 29 | public R apply(Function entityManagerFunction) { 30 | return entityManagerFunction.apply(em); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/StatefulWithExtended1.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import java.util.logging.Logger; 6 | import javax.ejb.LocalBean; 7 | import javax.ejb.Remove; 8 | import javax.ejb.Stateful; 9 | import javax.persistence.EntityManager; 10 | import javax.persistence.PersistenceContext; 11 | import javax.persistence.PersistenceContextType; 12 | 13 | /** 14 | * A {@code @Stateful} EJB that uses a container-managed entity manager with 15 | * extended persistence context.

16 | * 17 | * This EJB use default container-managed transactions.

18 | 19 | If a public method is invoked and the thread has a transaction bound to it, 20 | the transaction is used. Otherwise, a new transaction is created upon 21 | invocation and committed when the method apply end.

22 | * 23 | * The persistence context survive transaction boundaries and will be closed 24 | * only when the bean is destroyed/removed. 25 | * 26 | * @author Martin Andersson (webmaster at martinandersson.com) 27 | */ 28 | @Stateful 29 | @LocalBean 30 | public class StatefulWithExtended1 implements EntityManagerExposer 31 | { 32 | private static final Logger LOGGER = Logger.getLogger(StatefulWithExtended1.class.getName()); 33 | 34 | @PersistenceContext(type = PersistenceContextType.EXTENDED) 35 | EntityManager em; 36 | 37 | @Remove 38 | public void remove() {} 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public R apply(Function entityManagerFunction) { 45 | return entityManagerFunction.apply(em); 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/StatefulWithExtended2.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.ejb.LocalBean; 6 | import javax.ejb.Remove; 7 | import javax.ejb.Stateful; 8 | import javax.ejb.TransactionAttribute; 9 | import javax.ejb.TransactionAttributeType; 10 | import javax.persistence.EntityManager; 11 | import javax.persistence.PersistenceContext; 12 | import javax.persistence.PersistenceContextType; 13 | 14 | /** 15 | * A {@code @Stateful} EJB that uses a container-managed entity manager with 16 | * extended persistence context.

17 | * 18 | * Only the {@code @Remove} method in this EJB will use a transaction. 19 | * 20 | * @author Martin Andersson (webmaster at martinandersson.com) 21 | */ 22 | @Stateful 23 | @LocalBean 24 | public class StatefulWithExtended2 implements EntityManagerExposer 25 | { 26 | @PersistenceContext(type = PersistenceContextType.EXTENDED) 27 | EntityManager em; 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 33 | @Override 34 | public R apply(Function entityManagerFunction) { 35 | return entityManagerFunction.apply(em); 36 | } 37 | 38 | @TransactionAttribute(TransactionAttributeType.REQUIRED) 39 | @Remove 40 | public void remove() {} 41 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/StatefulWithExtended3.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.ejb.LocalBean; 6 | import javax.ejb.Remove; 7 | import javax.ejb.Stateful; 8 | import javax.ejb.TransactionAttribute; 9 | import javax.ejb.TransactionAttributeType; 10 | import javax.persistence.EntityManager; 11 | import javax.persistence.PersistenceContext; 12 | import javax.persistence.PersistenceContextType; 13 | 14 | /** 15 | * A {@code @Stateful} EJB that uses a container-managed entity manager with 16 | * extended persistence context.

17 | * 18 | * No methods in this EJB use transactions. If client has one when calling this 19 | * EJB, the transaction will be suspended. 20 | * 21 | * @author Martin Andersson (webmaster at martinandersson.com) 22 | */ 23 | @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) 24 | @Stateful 25 | @LocalBean 26 | public class StatefulWithExtended3 implements EntityManagerExposer 27 | { 28 | @PersistenceContext(type = PersistenceContextType.EXTENDED) 29 | EntityManager em; 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | @Override 35 | public R apply(Function entityManagerFunction) { 36 | return entityManagerFunction.apply(em); 37 | } 38 | 39 | @Remove 40 | public void remove() {} 41 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/StatelessWithExtended.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.ejb.LocalBean; 6 | import javax.ejb.Stateless; 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | import javax.persistence.PersistenceContextType; 10 | 11 | /** 12 | * This bean is a {@code @Stateless} EJB that try to use an entity manager of 13 | * type {@code PersistenceContextType.EXTENDED}. 14 | * 15 | * @author Martin Andersson (webmaster at martinandersson.com) 16 | */ 17 | @Stateless 18 | @LocalBean 19 | public class StatelessWithExtended implements EntityManagerExposer 20 | { 21 | @PersistenceContext(type = PersistenceContextType.EXTENDED) 22 | EntityManager em; 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | @Override 28 | public R apply(Function entityManagerFunction) { 29 | return entityManagerFunction.apply(em); 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/TransactionalWithExtended.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.persistence.EntityManager; 6 | import javax.persistence.PersistenceContext; 7 | import javax.persistence.PersistenceContextType; 8 | import javax.transaction.Transactional; 9 | 10 | /** 11 | * This bean is a {@code @Transactional} CDI bean that try to use an entity 12 | * manager of type {@code PersistenceContextType.EXTENDED}. 13 | * 14 | * @author Martin Andersson (webmaster at martinandersson.com) 15 | */ 16 | @Transactional 17 | public class TransactionalWithExtended implements EntityManagerExposer 18 | { 19 | @PersistenceContext(type = PersistenceContextType.EXTENDED) 20 | EntityManager em; 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | @Override 26 | public R apply(Function entityManagerFunction) { 27 | return entityManagerFunction.apply(em); 28 | } 29 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/entitymanagers/lib/UnsynchronizedEM.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.entitymanagers.lib; 2 | 3 | import com.martinandersson.javaee.jpa.entitymanagers.EntityManagerExposer; 4 | import java.util.function.Function; 5 | import javax.ejb.LocalBean; 6 | import javax.ejb.Stateless; 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | import javax.persistence.SynchronizationType; 10 | 11 | /** 12 | * A {@code @Stateless} bean whose entity manager is of {@code 13 | * SynchronizationType UNSYNCHRONIZED}. 14 | * 15 | * @author Martin Andersson (webmaster at martinandersson.com) 16 | */ 17 | @Stateless 18 | @LocalBean 19 | public class UnsynchronizedEM implements EntityManagerExposer 20 | { 21 | @PersistenceContext(synchronization = SynchronizationType.UNSYNCHRONIZED) 22 | EntityManager em; 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | @Override 28 | public R apply(Function entityManagerFunction) { 29 | return entityManagerFunction.apply(em); 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/elementcollection/ElementCollectionDefaultMappingTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.elementcollection; 2 | 3 | import com.martinandersson.javaee.jpa.mapping.elementcollection.lib.Repository; 4 | import com.martinandersson.javaee.jpa.mapping.elementcollection.lib.Song; 5 | import com.martinandersson.javaee.resources.SchemaGenerationStrategy; 6 | import com.martinandersson.javaee.utils.DeploymentBuilder; 7 | import javax.ejb.EJB; 8 | import org.jboss.arquillian.container.test.api.Deployment; 9 | import org.jboss.arquillian.junit.Arquillian; 10 | import org.jboss.shrinkwrap.api.Archive; 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertTrue; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | /** 17 | * Fails on WildFly/Hibernate (8.1.0 and 8.2.0).

18 | * 19 | * TODO: Report bug. 20 | * 21 | * @see com.martinandersson.javaee.jpa.mapping.elementcollection 22 | * 23 | * @author Martin Andersson (webmaster at martinandersson.com) 24 | */ 25 | @RunWith(Arquillian.class) 26 | public class ElementCollectionDefaultMappingTest 27 | { 28 | @Deployment 29 | private static Archive buildArchive() { 30 | return new DeploymentBuilder(ElementCollectionDefaultMappingTest.class) 31 | .addPersistenceXMLFile(SchemaGenerationStrategy.DROP_CREATE) 32 | .add(Song.class, Repository.class) 33 | .build(); 34 | } 35 | 36 | @EJB 37 | Repository songs; 38 | 39 | @Test 40 | public void elementCollectionOptional() { 41 | Song created = new Song("Michael Jackson", "Madonna Louise Ciccone"); 42 | songs.persist(created); 43 | 44 | final long id = created.getId(); 45 | assertTrue(id > 0L); 46 | 47 | songs.clearCaches(); 48 | 49 | Song found = songs.findById(Song.class, id); 50 | 51 | // This will also do equality check of all the producers of Song: 52 | assertEquals("Song actually created", 53 | created, found); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/elementcollection/ElementCollectionSeparateTableTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.elementcollection; 2 | 3 | import com.martinandersson.javaee.jpa.mapping.elementcollection.lib.Person; 4 | import com.martinandersson.javaee.jpa.mapping.elementcollection.lib.Repository; 5 | import com.martinandersson.javaee.resources.SchemaGenerationStrategy; 6 | import com.martinandersson.javaee.utils.DeploymentBuilder; 7 | import java.util.List; 8 | import javax.ejb.EJB; 9 | import org.jboss.arquillian.container.test.api.Deployment; 10 | import org.jboss.arquillian.junit.Arquillian; 11 | import org.jboss.arquillian.junit.InSequence; 12 | import org.jboss.shrinkwrap.api.Archive; 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertNotNull; 15 | import static org.junit.Assert.assertTrue; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | 19 | /** 20 | * @see com.martinandersson.javaee.jpa.mapping.elementcollection 21 | * 22 | * @author Martin Andersson (webmaster at martinandersson.com) 23 | */ 24 | @RunWith(Arquillian.class) 25 | public class ElementCollectionSeparateTableTest 26 | { 27 | @Deployment 28 | private static Archive buildArchive() { 29 | return new DeploymentBuilder(ElementCollectionSeparateTableTest.class) 30 | .addPersistenceXMLFile(SchemaGenerationStrategy.DROP_CREATE) 31 | .add(Person.class, 32 | Repository.class) 33 | .build(); 34 | } 35 | 36 | 37 | @EJB 38 | Repository persons; 39 | 40 | static long id; 41 | 42 | 43 | @Test 44 | @InSequence(1) 45 | public void test_collectionInSeparateTable() { 46 | Person created = new Person("Some nick", "Another nick"); 47 | persons.persist(created); 48 | 49 | id = created.getId(); 50 | assertTrue(id > 0L); 51 | 52 | persons.clearCaches(); 53 | 54 | Person found = persons.findById(Person.class, id); 55 | 56 | // This will also do equality check of all the nicknames of Person: 57 | assertEquals("Person actually created", created, found); 58 | } 59 | 60 | /** 61 | * GlassFish 4.1 and WildFly 8.2.0 remove nickname orphans from the 62 | * collection table. 63 | */ 64 | @Test 65 | @InSequence(2) 66 | public void test_orphanRemoval() { 67 | Person person = persons.findById(Person.class, id); 68 | assertNotNull(person); 69 | persons.remove(person); 70 | persons.clearCaches(); 71 | 72 | List nicks = persons.apply(em -> 73 | em.createNativeQuery( 74 | "SELECT NICKNAMES FROM JPA_MAPPING_ELEMENTCOLLECTION.PERSON_NICKNAMES") 75 | .getResultList()); 76 | 77 | assertTrue(nicks.isEmpty()); 78 | } 79 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/elementcollection/lib/Person.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.elementcollection.lib; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import javax.persistence.CollectionTable; 8 | import javax.persistence.ElementCollection; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | import javax.persistence.NamedAttributeNode; 13 | import javax.persistence.NamedEntityGraph; 14 | import javax.persistence.Table; 15 | 16 | /** 17 | * Is a JPA entity with a {@code Set} attribute annotated 18 | * {@code @ElementCollection}. 19 | * 20 | * @author Martin Andersson (webmaster at martinandersson.com) 21 | */ 22 | @Entity 23 | @NamedEntityGraph(attributeNodes = @NamedAttributeNode("nicknames")) // <-- see comment in Repository.findById() 24 | @Table(schema="JPA_MAPPING_ELEMENTCOLLECTION") 25 | public class Person 26 | { 27 | @Id 28 | @GeneratedValue 29 | private long id; 30 | 31 | @ElementCollection // <-- make the collection go to a separate table 32 | @CollectionTable(schema = "JPA_MAPPING_ELEMENTCOLLECTION") // <-- override where to put the table 33 | private Set nicknames; 34 | 35 | protected Person() { 36 | // Empty 37 | } 38 | 39 | public Person(String... nicknames) { 40 | this.nicknames = new HashSet<>(Arrays.asList(nicknames)); 41 | } 42 | 43 | public long getId() { 44 | return id; 45 | } 46 | 47 | @Override 48 | public boolean equals(Object obj) { 49 | return this == obj || obj instanceof Person ? 50 | /* 51 | * Doing nicknames here only because we're in a test environment. 52 | * A good design for Java EE apps would otherwise be to only 53 | * rely on id and possibly type too. 54 | */ 55 | id == ((Person) obj).id && Objects.equals(nicknames, ((Person) obj).nicknames) : 56 | false; 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/elementcollection/lib/Song.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.elementcollection.lib; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import javax.persistence.NamedAttributeNode; 11 | import javax.persistence.NamedEntityGraph; 12 | import javax.persistence.Table; 13 | 14 | /** 15 | * Is a JPA entity with an unannotated {@code Set} attribute. 16 | * 17 | * @author Martin Andersson (webmaster at martinandersson.com) 18 | */ 19 | @Entity 20 | @NamedEntityGraph(attributeNodes = @NamedAttributeNode("producers")) // <-- see comment in Repository.findById(Class, long) 21 | @Table(schema="JPA_MAPPING_ELEMENTCOLLECTION") 22 | public class Song 23 | { 24 | @Id 25 | @GeneratedValue 26 | private long id; 27 | 28 | private Set producers; 29 | 30 | protected Song() { 31 | 32 | } 33 | 34 | public Song(String... producers) { 35 | this.producers = new HashSet<>(Arrays.asList(producers)); 36 | } 37 | 38 | public long getId() { 39 | return id; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object obj) { 44 | return this == obj || obj instanceof Song ? 45 | /* 46 | * Doing producers here only because we're in a test environment. 47 | * A good design for Java EE apps would otherwise be to only 48 | * rely on id and possibly type too. 49 | */ 50 | id == ((Song) obj).id && Objects.equals(producers, ((Song) obj).producers) : 51 | false; 52 | } 53 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/elementcollection/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * JPA 2.1, section "2.6 Collections of Embeddable Classes and Basic Types": 3 | *

{@code
 4 |  * 
 5 |  *     A persistent field or property of an entity or embeddable class may
 6 |  *     correspond to a collection of a basic type or embeddable class ("element
 7 |  *     collection"). Such a collection, when specified as such by the
 8 |  *     ElementCollection annotation, is mapped by means of a collection table,
 9 |  *     as defined in Section 11.1.8. If the ElementCollection annotation (or XML
10 |  *     equivalent) is not specified for the collection-valued field or property,
11 |  *     the rules of Section 2.8 apply.
12 |  * 
13 |  * }
14 | * 15 | * 16 | * Section 2.8 doesn't list the field type {@code Set}. Section "11.1.14 17 | * ElementCollection Annotation" says: 18 | *
{@code
19 |  * 
20 |  *     The ElementCollection annotation (or equivalent XML element) must be
21 |  *     specified if the collection is to be mapped by means of a collection
22 |  *     table.
23 |  * 
24 |  * }
25 | * 26 | * 27 | * My understanding of section 2.6 and 11.1.14 is that {@code @ElementCollection} 28 | * is optional and may be left out. Only if added shall the collection be mapped 29 | * to his own table. GlassFish and EclipseLink work in this way, WildFly and 30 | * Hibernate crash upon deployment.

31 | * 32 | * In order to make at least one of the provided two tests work on WildFly (the 33 | * one that uses {@code @ElementCollection}; {@code 34 | * ElementCollectionSeparateTableTest}), they have been split into separate 35 | * files and separate deployments.

36 | * 37 | * Using Arquillian's techniques for multiple deployments (i.e. 38 | * {@code @Deployment(name = "name")} and {@code @OperateOnDeployment("name")}) 39 | * won't bypass this issue.

40 | * 41 | * Bug filed here: 42 | *

{@code
43 |  * 
44 |  *     https://hibernate.atlassian.net/browse/HHH-9402
45 |  * 
46 |  * }
47 | */ 48 | package com.martinandersson.javaee.jpa.mapping.elementcollection; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/AbstractId.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.orphanremoval; 2 | 3 | import javax.persistence.GeneratedValue; 4 | import javax.persistence.Id; 5 | import javax.persistence.MappedSuperclass; 6 | 7 | /** 8 | * 9 | * @author Martin Andersson (webmaster at martinandersson.com) 10 | */ 11 | @MappedSuperclass 12 | public abstract class AbstractId 13 | { 14 | @Id 15 | @GeneratedValue 16 | private long id; 17 | 18 | public long getId() { 19 | return id; 20 | } 21 | 22 | @Override 23 | public int hashCode() { 24 | return Long.hashCode(id); 25 | } 26 | 27 | @Override 28 | public boolean equals(Object other) { 29 | return this == other || 30 | other instanceof AbstractId && this.id == ((AbstractId) other).id; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return getClass().getSimpleName() + "[id=" + id + "]"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/CascadeNone.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.orphanremoval; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | @Entity 11 | @Table(name = "CASCADE_NONE", schema = OrphanRemovalTest.SCHEMA) 12 | public class CascadeNone extends AbstractId 13 | { 14 | // Empty 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/CascadeRemove.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.orphanremoval; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | @Entity 11 | @Table(name = "CASCADE_REMOVE", schema = OrphanRemovalTest.SCHEMA) 12 | public class CascadeRemove extends AbstractId 13 | { 14 | // Empty 15 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/OrphanRemoval.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.orphanremoval; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author Martin Andersson (webmaster at martinandersson.com) 9 | */ 10 | @Entity 11 | @Table(name = "ORPHAN_REMOVAL", schema = OrphanRemovalTest.SCHEMA) 12 | public class OrphanRemoval extends AbstractId 13 | { 14 | // Entity 15 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/Owner.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.orphanremoval; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Entity; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.JoinTable; 11 | import javax.persistence.OneToMany; 12 | import javax.persistence.Table; 13 | 14 | /** 15 | * 16 | * @author Martin Andersson (webmaster at martinandersson.com) 17 | */ 18 | @Entity 19 | @Table(schema = OrphanRemovalTest.SCHEMA) 20 | public class Owner extends AbstractId 21 | { 22 | @OneToMany(fetch = FetchType.EAGER) 23 | @JoinTable(schema = OrphanRemovalTest.SCHEMA) 24 | private Set nones = new HashSet<>(); 25 | 26 | @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE) 27 | @JoinTable(schema = OrphanRemovalTest.SCHEMA) 28 | private Set removes = new HashSet<>(); 29 | 30 | @OneToMany(fetch = FetchType.EAGER, orphanRemoval = true) 31 | @JoinTable(schema = OrphanRemovalTest.SCHEMA) 32 | private Set orphans = new HashSet<>(); 33 | 34 | 35 | 36 | public Set getCascadeNones() { 37 | return Collections.unmodifiableSet(nones); 38 | } 39 | 40 | public Set getCascadeRemoves() { 41 | return Collections.unmodifiableSet(removes); 42 | } 43 | 44 | public Set getOrphanRemovals() { 45 | return Collections.unmodifiableSet(orphans); 46 | } 47 | 48 | 49 | 50 | public boolean addCascadeNone(CascadeNone none) { 51 | return nones.add(Objects.requireNonNull(none)); 52 | } 53 | 54 | public boolean addCascadeRemove(CascadeRemove remove) { 55 | return removes.add(Objects.requireNonNull(remove)); 56 | } 57 | 58 | public boolean addOrphanRemoval(OrphanRemoval orphan) { 59 | return orphans.add(Objects.requireNonNull(orphan)); 60 | } 61 | 62 | 63 | 64 | public boolean removeCascadeNone(CascadeNone none) { 65 | return nones.remove(none); 66 | } 67 | 68 | public boolean removeCascadeRemove(CascadeRemove remove) { 69 | return removes.remove(remove); // <-- lol wtf. 70 | } 71 | 72 | public boolean removeOrphanRemoval(OrphanRemoval orphan) { 73 | return orphans.remove(orphan); 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | return new StringBuilder(Owner.class.getSimpleName()) 79 | .append('[') 80 | .append("nones=").append(nones) 81 | .append(", removes=").append(removes) 82 | .append(", orphans=").append(orphans) 83 | .append(']') 84 | .toString(); 85 | } 86 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jpa/mapping/orphanremoval/Repository.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jpa.mapping.orphanremoval; 2 | 3 | import java.util.function.Consumer; 4 | import javax.ejb.Stateless; 5 | import javax.persistence.EntityManager; 6 | import javax.persistence.PersistenceContext; 7 | 8 | /** 9 | * @author Martin Andersson (webmaster at martinandersson.com) 10 | */ 11 | @Stateless 12 | public class Repository 13 | { 14 | @PersistenceContext 15 | EntityManager em; 16 | 17 | public void applyWithEM(Consumer accept) { 18 | accept.accept(em); 19 | } 20 | 21 | public void persist(Object entity) { 22 | em.persist(entity); 23 | } 24 | 25 | public T merge(T entity) { 26 | return em.merge(entity); 27 | } 28 | 29 | public T find(Class type, Object id) { 30 | return em.find(type, id); 31 | } 32 | 33 | /** 34 | * Remove an entity of the provided type and id. 35 | * 36 | * @param type entity type 37 | * @param id entity id 38 | * 39 | * @throws javax.persistence.EntityNotFoundException if asked to remove an 40 | * entity that can not be found 41 | */ 42 | public void remove(Class type, Object id) { 43 | Object entity = em.getReference(type, id); 44 | em.remove(entity); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jta/listeners/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrate ways of listening for transaction life-cycle events and querying 3 | * the transaction status.

4 | * 5 | *

    6 | *
  • {@linkplain com.martinandersson.javaee.jta.listeners.synchronizationregistry.SynchronizationRegistryTest SynchronizationRegistryTest}
  • 7 | *
  • TODO: SessionSynchronization
  • 8 | *
9 | */ 10 | package com.martinandersson.javaee.jta.listeners; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jta/listeners/synchronizationregistry/ManagedBean.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jta.listeners.synchronizationregistry; 2 | 3 | import javax.annotation.Resource; 4 | import javax.transaction.TransactionSynchronizationRegistry; 5 | 6 | /** 7 | * @author Martin Andersson (webmaster at martinandersson.com) 8 | */ 9 | @javax.annotation.ManagedBean 10 | public class ManagedBean { 11 | @Resource(lookup = "java:comp/TransactionSynchronizationRegistry") 12 | TransactionSynchronizationRegistry txRegistry; 13 | 14 | public int getTransactionStatus() { 15 | return txRegistry.getTransactionStatus(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jta/listeners/synchronizationregistry/SynchronizationRegistryTest.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jta.listeners.synchronizationregistry; 2 | 3 | import com.martinandersson.javaee.jta.listeners.synchronizationregistry.TestDriver.Report; 4 | import com.martinandersson.javaee.utils.DeploymentBuilder; 5 | import com.martinandersson.javaee.utils.HttpRequests; 6 | import java.net.URL; 7 | import javax.transaction.Status; 8 | import org.jboss.arquillian.container.test.api.Deployment; 9 | import org.jboss.arquillian.container.test.api.RunAsClient; 10 | import org.jboss.arquillian.junit.Arquillian; 11 | import org.jboss.arquillian.junit.InSequence; 12 | import org.jboss.arquillian.test.api.ArquillianResource; 13 | import org.jboss.shrinkwrap.api.Archive; 14 | import static org.junit.Assert.assertEquals; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | 18 | /** 19 | * The {@code TransactionSynchronizationRegistry} offer a low-level transaction 20 | * API "not meant to be used by application programmers".

21 | * 22 | * But of course, such a statement assume that all servers do what they are 23 | * supposed to do, which is not always the case. The current state of 1) buggy 24 | * server software, 2) an insufficient API and 3) the enforcement of programming 25 | * models upon others, require the developer to get his hands dirty and use the 26 | * registry from time to time.

27 | * 28 | * TODO: This "test" is currently very incomplete. 29 | * 30 | * @author Martin Andersson (webmaster at martinandersson.com) 31 | */ 32 | @RunWith(Arquillian.class) 33 | public class SynchronizationRegistryTest 34 | { 35 | @Deployment 36 | private static Archive buildDeployment() { 37 | return new DeploymentBuilder(SynchronizationRegistryTest.class) 38 | .addEmptyBeansXMLFile() 39 | .addTestPackage() 40 | .build(); 41 | } 42 | 43 | 44 | 45 | static Report report; // = initialized in __callDriver(). 46 | 47 | @Test 48 | @RunAsClient 49 | @InSequence(1) 50 | public void __callDriver(@ArquillianResource URL url) { 51 | report = HttpRequests.getObject(url); 52 | } 53 | 54 | 55 | 56 | /* 57 | * ------------ 58 | * | REAL TESTS | 59 | * ------------ 60 | */ 61 | 62 | @Test 63 | @RunAsClient 64 | @InSequence(2) 65 | public void managedBeanTXStatus() { 66 | assertEquals("Expected that a non-transactional managed bean has no active transaction. Transaction status", 67 | Status.STATUS_NO_TRANSACTION /* = 6 */, report.managedBeanTXStatus); 68 | } 69 | 70 | @Test 71 | @RunAsClient 72 | @InSequence(2) 73 | public void txManagedBeanTXStatus() { 74 | assertEquals("Expected that a @Transactional managed bean uses transactions. Transaction status", 75 | Status.STATUS_ACTIVE /* = 0 */, report.txManagedBeanTXStatus); 76 | } 77 | 78 | @Test 79 | @RunAsClient 80 | @InSequence(2) 81 | public void txManagedBeanTXStatusBeforeCompletion() { 82 | assertEquals("Expected that before completion, transaction was active. Transaction status", 83 | Status.STATUS_ACTIVE /* = 0 */, report.txManagedBeanTXStatusBeforeCompletion); 84 | } 85 | 86 | @Test 87 | @RunAsClient 88 | @InSequence(2) 89 | public void txManagedBeanTXStatusAfterCompletion() { 90 | assertEquals("Expected that after completion, transaction was committed. Transaction status", 91 | Status.STATUS_COMMITTED /* = 3 */, report.txManagedBeanTXStatusAfterCompletion); 92 | } 93 | 94 | @Test 95 | @RunAsClient 96 | @InSequence(2) 97 | public void txManagedBeanTXStatusAfterSuspension() { 98 | assertEquals("Expected that after suspension, no transaction is active. Transaction status", 99 | Status.STATUS_NO_TRANSACTION /* = 6 */, report.txManagedBeanTxStatusAfterSuspension); 100 | } 101 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jta/listeners/synchronizationregistry/TestDriver.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jta.listeners.synchronizationregistry; 2 | 3 | import java.io.IOException; 4 | import java.io.ObjectOutputStream; 5 | import java.io.Serializable; 6 | import javax.annotation.Resource; 7 | import javax.inject.Inject; 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 | import javax.transaction.HeuristicMixedException; 14 | import javax.transaction.HeuristicRollbackException; 15 | import javax.transaction.NotSupportedException; 16 | import javax.transaction.RollbackException; 17 | import javax.transaction.SystemException; 18 | import javax.transaction.UserTransaction; 19 | 20 | /** 21 | * @author Martin Andersson (webmaster at martinandersson.com) 22 | */ 23 | @WebServlet("") 24 | public class TestDriver extends HttpServlet 25 | { 26 | @Resource 27 | ManagedBean managedBean; 28 | 29 | @Resource 30 | TransactionalManagedBean txManagedBean; 31 | 32 | @Inject 33 | UserTransaction tx; 34 | 35 | @Override 36 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) 37 | throws ServletException, IOException 38 | { 39 | final Report report = new Report(); 40 | Object response; 41 | 42 | report.managedBeanTXStatus = managedBean.getTransactionStatus(); 43 | report.txManagedBeanTXStatus = txManagedBean.getTransactionStatus(); 44 | 45 | txManagedBean.registerListeners( 46 | beforeStatus -> report.txManagedBeanTXStatusBeforeCompletion = beforeStatus, 47 | afterStatus -> report.txManagedBeanTXStatusAfterCompletion = afterStatus); 48 | 49 | try { 50 | tx.begin(); 51 | report.txManagedBeanTxStatusAfterSuspension = txManagedBean.getTransactionStatusAfterSuspension(); 52 | response = report; 53 | } 54 | catch (NotSupportedException | SystemException e) { 55 | response = e; 56 | } 57 | finally { 58 | try { 59 | tx.commit(); 60 | } 61 | catch (RollbackException | 62 | HeuristicMixedException | 63 | HeuristicRollbackException | 64 | SecurityException | 65 | IllegalStateException | 66 | SystemException e) 67 | { 68 | response = report; 69 | } 70 | } 71 | 72 | new ObjectOutputStream(resp.getOutputStream()).writeObject(response); 73 | } 74 | 75 | static class Report implements Serializable { 76 | int managedBeanTXStatus, 77 | txManagedBeanTXStatus, 78 | txManagedBeanTXStatusBeforeCompletion, 79 | txManagedBeanTXStatusAfterCompletion, 80 | txManagedBeanTxStatusAfterSuspension; 81 | } 82 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/jta/listeners/synchronizationregistry/TransactionalManagedBean.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.jta.listeners.synchronizationregistry; 2 | 3 | import java.util.function.IntConsumer; 4 | import java.util.logging.Logger; 5 | import javax.annotation.Resource; 6 | import javax.transaction.Synchronization; 7 | import javax.transaction.TransactionSynchronizationRegistry; 8 | import javax.transaction.Transactional; 9 | 10 | /** 11 | * @author Martin Andersson (webmaster at martinandersson.com) 12 | */ 13 | @Transactional 14 | @javax.annotation.ManagedBean 15 | public class TransactionalManagedBean { 16 | private static final Logger LOGGER 17 | = Logger.getLogger(TransactionalManagedBean.class.getName()); 18 | 19 | @Resource(lookup = "java:comp/TransactionSynchronizationRegistry") 20 | TransactionSynchronizationRegistry txRegistry; 21 | 22 | /* 23 | * Both of these methods are implicitly: @Transactional(Transactional.TxType.REQUIRED), 24 | * meaning that a new transaction will be started during each invocation and committed 25 | * when each method finish. 26 | */ 27 | 28 | public int getTransactionStatus() { 29 | return txRegistry.getTransactionStatus(); 30 | } 31 | 32 | public void registerListeners( 33 | IntConsumer beforeCompletionStatus, IntConsumer afterCompletionStatus) 34 | { 35 | txRegistry.registerInterposedSynchronization(new Synchronization() { 36 | 37 | @Override public void beforeCompletion() { 38 | beforeCompletionStatus.accept(txRegistry.getTransactionStatus()); 39 | } 40 | 41 | @Override public void afterCompletion(int status) { 42 | afterCompletionStatus.accept(status); 43 | } 44 | }); 45 | } 46 | 47 | @Transactional(Transactional.TxType.NOT_SUPPORTED) 48 | public int getTransactionStatusAfterSuspension() { 49 | return txRegistry.getTransactionStatus(); 50 | } 51 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Proposed order of study (always begin with the {@code package-info.java} 3 | * file, if one exists): 4 | * 5 | *

    6 | *
  1. Testing Java EE applications
    7 | * {@code com.martinandersson.javaee.arquillian}

  2. 8 | *
  3. Contexts and Dependency Injection
    9 | * {@code com.martinandersson.javaee.cdi}

  4. 10 | *
  5. Something else
    11 | * com.martinandersson.javaee.xxx ..
  6. 12 | *
13 | */ 14 | package com.martinandersson.javaee; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/resources/ArquillianDS.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.resources; 2 | 3 | import javax.annotation.ManagedBean; 4 | import javax.annotation.sql.DataSourceDefinition; 5 | 6 | /** 7 | * A POJO written only for the purpose of defining a data source, easily 8 | * attachable to a deployment archive. 9 | * 10 | * @author Martin Andersson (webmaster at martinandersson.com) 11 | */ 12 | @DataSourceDefinition( // note 1 13 | name = "java:app/env/ArquillianDS", 14 | className = "org.apache.derby.jdbc.ClientXADataSource", 15 | serverName = "localhost", 16 | portNumber = 1527, 17 | databaseName = "arquillian-test-db", 18 | user = "app", 19 | password = "app", 20 | properties = {"connectionAttributes=;create=true"}) 21 | @ManagedBean // <-- note 2 22 | public class ArquillianDS {} 23 | 24 | /* 25 | * Note 1: Instead of explicitly providing all the properties, I would like to 26 | * provide one single connection url like so: 27 | * url = "jdbc:derby://localhost:1527/arquillian-test-db;create=true;user=app;password=app" 28 | * 29 | * However, GlassFish cannot parse the url property correctly. See: 30 | * https://java.net/jira/browse/GLASSFISH-20773 31 | * 32 | * Note 2: @DataSourceDefinition must be put on a managed bean. Here, the 33 | * ArquillianDS class has explicitly been marked as a managed bean. A 34 | * @Stateless bean or any other server side component such as a Servlet 35 | * is implicitly a "managed bean" too. 36 | */ -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/resources/SchemaGenerationStrategy.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.resources; 2 | 3 | /** 4 | * Strategy for database schema generation.

5 | * 6 | * Available strategies: 7 | * 8 | *

    9 | *
  • {@linkplain #UPDATE}
  • 10 | *
  • {@linkplain #DROP_CREATE}
  • 11 | *
12 | * 13 | * @see com.martinandersson.javaee.utils.Deployments#buildPersistenceArchive(SchemaGenerationStrategy, Class, Class...) 14 | * 15 | * @author Martin Andersson (webmaster at martinandersson.com) 16 | */ 17 | public enum SchemaGenerationStrategy 18 | { 19 | /** 20 | * All tables will be 1) created if not present or 2) used if already 21 | * present and 3) updated if need be.

22 | * 23 | * This strategy work homogenously on EclipseLink as well as Hibernate.

24 | * 25 | * Using this strategy will make file 26 | * {@code ./src/test/resources/persistence-update.xml} be deployed in the 27 | * test archive as {@code META-INF/persistence.xml}. 28 | */ 29 | UPDATE("persistence-update.xml"), 30 | 31 | /** 32 | * All tables will be 1) dropped and then 2) created.

33 | * 34 | * WildFly that uses Hibernate has a third side-effect: Hibernate drop the 35 | * tables after the test. Meaning that you cannot explore the tables and 36 | * data in them after the test is done. If that is what you want, then use 37 | * GlassFish/EclipseLink.

38 | * 39 | * Using this strategy will make file 40 | * {@code ./src/test/resources/persistence-dropcreate.xml} be deployed in 41 | * the test archive as {@code META-INF/persistence.xml}. 42 | */ 43 | DROP_CREATE("persistence-dropcreate.xml"); 44 | 45 | private final String filename; 46 | 47 | SchemaGenerationStrategy(String filename) { 48 | this.filename = filename; 49 | } 50 | 51 | /** 52 | * Returns the filename of the persistence unit configuration file. 53 | * 54 | * @return the filename of the persistence unit configuration file 55 | */ 56 | public String getFilename() { 57 | return filename; 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/resources/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Package for programmatically deployable server resources. 3 | */ 4 | package com.martinandersson.javaee.resources; -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/utils/Deployments.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.utils; 2 | 3 | import java.util.Set; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | import javax.enterprise.inject.spi.Extension; 9 | import org.jboss.shrinkwrap.api.Archive; 10 | import org.jboss.shrinkwrap.api.ShrinkWrap; 11 | import org.jboss.shrinkwrap.api.container.LibraryContainer; 12 | import org.jboss.shrinkwrap.api.container.ManifestContainer; 13 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 14 | 15 | /** 16 | * Utility API for deployments. 17 | * 18 | * @author Martin Andersson (webmaster at martinandersson.com) 19 | */ 20 | public final class Deployments 21 | { 22 | private static final Logger LOGGER = Logger.getLogger(Deployments.class.getName()); 23 | 24 | private Deployments() { 25 | // Empty 26 | } 27 | 28 | 29 | /** 30 | * Install the specified extension as a CDI extension in the specified 31 | * archive. 32 | * 33 | * @param archive type 34 | * @param archive the target archive 35 | * @param extension what to install 36 | * 37 | * @return same archive as specified 38 | */ 39 | public static 40 | & ManifestContainer & LibraryContainer> 41 | T installCDIExtension(T archive, Class extension) 42 | { 43 | // Backup what has already been printed to log 44 | final Set before = LOGGER.isLoggable(Level.INFO) ? 45 | toSet(archive) : null; 46 | 47 | archive.addAsServiceProvider(javax.enterprise.inject.spi.Extension.class, extension); 48 | 49 | // Build CDIExtension library and put in provided archive 50 | JavaArchive lib = ShrinkWrap.create(JavaArchive.class, extension.getSimpleName() + ".jar") 51 | .addClass(extension); 52 | 53 | archive.addAsLibrary(lib); 54 | 55 | LOGGER.info(() -> { 56 | Set after = toSet(archive); 57 | after.removeAll(before); 58 | 59 | return "Installed CDI extension in " + archive.getName() + ":\n" + 60 | after.stream().sorted().collect( 61 | Collectors.joining(System.lineSeparator())); 62 | }); 63 | 64 | return archive; 65 | } 66 | 67 | 68 | 69 | /* 70 | * -------------- 71 | * | INTERNAL API | 72 | * -------------- 73 | */ 74 | 75 | /** 76 | * Converts the contents of the provided archive to a Set of strings, paths 77 | * separated with newlines. 78 | * 79 | * @param type of archive 80 | * @param archive the archive to supposedly log 81 | * 82 | * @return "setified" archive 83 | */ 84 | private static > Set toSet(T archive) { 85 | return Stream.of(toString(archive).split("\n")).collect(Collectors.toSet()); 86 | } 87 | 88 | /** 89 | * Converts the contents of the provided archive to a String, paths 90 | * separated with newlines. 91 | * 92 | * @param type of archive 93 | * @param archive the archive to supposedly log 94 | * 95 | * @return stringified archive 96 | */ 97 | private static > String toString(T archive) { 98 | return archive.toString(true).replace("\n", "\n\t"); 99 | } 100 | } -------------------------------------------------------------------------------- /src/test/java/com/martinandersson/javaee/utils/Lookup.java: -------------------------------------------------------------------------------- 1 | package com.martinandersson.javaee.utils; 2 | 3 | import javax.naming.InitialContext; 4 | import javax.naming.NamingException; 5 | import javax.transaction.TransactionSynchronizationRegistry; 6 | import javax.transaction.UserTransaction; 7 | 8 | /** 9 | * Utility class with an API to look things up using JNDI. 10 | * 11 | * @author Martin Andersson (webmaster at martinandersson.com) 12 | */ 13 | public final class Lookup 14 | { 15 | private Lookup() { 16 | // Utility class 17 | } 18 | 19 | public static T globalBean(Class beanType) throws NamingException { 20 | return InitialContext.doLookup("java:global/" + moduleName() + "/" + beanType.getSimpleName()); 21 | } 22 | 23 | public static String moduleName() throws NamingException { 24 | return InitialContext.doLookup("java:module/ModuleName"); 25 | } 26 | 27 | public static TransactionSynchronizationRegistry transactionSyncRegistry() throws NamingException { 28 | return InitialContext.doLookup("java:comp/TransactionSynchronizationRegistry"); 29 | } 30 | 31 | public static UserTransaction userTransaction() throws NamingException { 32 | return InitialContext.doLookup("java:comp/UserTransaction"); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | target/deployments 9 | 10 | 11 | 15 | 16 | 28 | 29 | -------------------------------------------------------------------------------- /src/test/resources/ejb-jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | java:app/env/ArquillianDS 14 | org.apache.derby.jdbc.ClientXADataSource 15 | localhost 16 | 1527 17 | arquillian-test-db 18 | app 19 | app 20 | 21 | connectionAttributes 22 | ;create=true 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/persistence-dropcreate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | 16 | java:app/env/ArquillianDS 17 | false 18 | DISABLE_SELECTIVE 19 | 20 | 21 | 22 | 27 | 28 | 29 | 33 | 34 | 35 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/test/resources/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | java:app/env/ArquillianDS 14 | org.apache.derby.jdbc.ClientXADataSource 15 | localhost 16 | 1527 17 | arquillian-test-db 18 | app 19 | app 20 | 21 | connectionAttributes 22 | ;create=true 23 | 24 | 25 | --------------------------------------------------------------------------------