PersistenceUnitInfo implementation in EclipseLink - jpa

EntityManagerFactory can be created without a persistence unit xml using
org.eclipse.persistence.jpa.PersistenceProvider {
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
java.util.Map properties)
}
but what is the implementation class of javax.persistence.spi.PersistenceUnitInfo in eclipselink

I am struggling on this problem too. I think that a PersistenceUnitInfo must be provided by the container(i.e. Application Server). It means that Eclipselink do not create one itself. If you are using Spring ORM, it uses a DefaultPersistenceUnitManager and call its obtainPersistenceUnitInfo(String unitName) method to get a instance of PersistenceUnitInfo. The unitName must be defined in persistence.xml. This means that you still needs an xml file.
By digging into the source code of Spring ORM, I found that Spring provides several implementations of PersistenceUnitInfo. In fact they are generally a Java Bean. You may be interested in SmartPersistenceInfo, MutablePersistenceInfo and SpringPersistenceUnitInfo. View them on Github.
EDIT:
I found the implementation of Eclipselink: It's SEPersistenceUnitInfo in org.eclipse.persistence.internal.jpa.deployment. Also found the method that reads every persistence unit in the configuration xml file.
public static Set<SEPersistenceUnitInfo> getPersistenceUnits(ClassLoader loader, Map m, List<URL> jarFileUrls) {
String descriptorPath = (String) m.get(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML);
if(descriptorPath == null) {
descriptorPath = System.getProperty(PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML, PersistenceUnitProperties.ECLIPSELINK_PERSISTENCE_XML_DEFAULT);
}
Set<Archive> archives = findPersistenceArchives(loader, descriptorPath, jarFileUrls);
Set<SEPersistenceUnitInfo> puInfos = new HashSet();
try {
for(Archive archive : archives) {
List<SEPersistenceUnitInfo> puInfosFromArchive = getPersistenceUnits(archive, loader);
puInfos.addAll(puInfosFromArchive);
}
} finally {
for(Archive archive : archives) {
archive.close();
}
}
return puInfos;
}

Java EE platform spec 6 says : the container is responsible for finding persistence.xml condensing the information into PersistenceUnitInfo and supplying that with a call to createContainerEntityManagerFactory.

PersistenceUnitInfo is defined by the Spec, refer to the JPA spec code or JavaDoc for its implementation.
http://www.eclipse.org/eclipselink/api/2.5/javax/persistence/spi/PersistenceUnitInfo.html

Related

Write/Run Junit Test class (To test actuators) without Datasource bean creation in springboot container

What we are trying to do?
Writing a Junit for Springboot actuator/Admin as below
Code snippet:
ActuatorTests.java
#SpringBootTest(properties = {
"management.endpoints.web.exposure.include=" })
#ActiveProfiles(profiles = "local")
#AutoConfigureMockMvc
public class ActuatorTests {
#Autowired
private MockMvc mockMvc;
#MockBean
JwtDecoder jwtDecoder;
#Test
public void testActuatorEndpointSuccess() throws Exception {
MockHttpServletResponse resp = mockMvc
.perform(MockMvcRequestBuilders.get("/actuator/").accept(MediaType.APPLICATION_JSON)).andReturn()
.getResponse();
assertEquals(resp.getStatus(), 200);
}
application-local.yml
This property contains Datasource, username, password and others properties
What is the issue?
During spring boot container start, it is creating Data Source by using data source properties of application-local.yml
Problem here is I can't rely on application-local.yml becoz properties changes environment to environment may not work all the time with same property values and also which is unnecessary for my Junit as the testcase is about testing the management actuator endpoint only.
What we have tried?
Ran by excluding some the JPA classes using below.
#SpringBootTest(properties = {
"management.endpoints.web.exposure.include=" })
#ActiveProfiles(profiles = "local")
#AutoConfigureMockMvc
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
public class ActuatorTests { .....}
But found the below error in the console.
Note: the error log also having chain of bean creation errors from DAO,Service, to Controller layer classes,
I have given only the tail of the log due to restrictions.
**Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available**
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:805)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1278)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:297)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:276)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
... 118 common frames omitted
Any help on this?
We can see similar question has been asked but no answer found in it.
Run junit without data source persistence context in spring boot
Any other solution to above actuator Test Junit is also welcome..

How to inject spring aop advice for MongoDb call?

I am new to Spring Aop, but I have case to implement AOP advice for a mongo db call(monog db update). I am trying in different way but getting 'Point cut not well formed' error or 'warning no match for this type name: arg string [Xlint:invalidAbsoluteTypeName]'(even if I give absolute name of the argument). Anyone can help on this as how to inject advice for mongo db update call?
#Aspect
#Component
public class DBStatsLoggerAspect {
private static final Logger log = LoggerFactory
.getLogger(DBStatsLoggerAspect.class);
private static final Document reqStatsCmdBson = new Document(
"getLastRequestStatistics", 1);
private DbCallback<Document> requestStatsDbCallback = new DbCallback<Document>() {
#Override
public Document doInDB(MongoDatabase db) throws MongoException,
DataAccessException {
return db.runCommand(reqStatsCmdBson);
}
};
#After("execution( public * com.mongodb.client.MongoCollection.*(..)) && args(org.bson.conversions.Bson.filter,..)")
public void requestStatsLoggerAdvice(JoinPoint joinPoint) {
MongoTemplate mongoTemplate = (MongoTemplate) joinPoint.getTarget();
log.info(mongoTemplate.execute(requestStatsDbCallback).toJson());
}
}
Actual db call method where I need to inject advice:(filter, updatePart all are org.bson.conversions.Bson data type) and here 'collection' is com.mongodb.client.MongoCollection.collection
Document result = collection.findOneAndUpdate(filter, updatePart, new FindOneAndUpdateOptions().upsert(false));
I am not a Spring or MongoDB user, just an AOP expert. But from what I see I am wondering:
You are intercepting execution(public * com.mongodb.client.MongoCollection.*(..)), so joinPoint.getTarget() is a MongoCollection type. Why do you think you can cast it to MongoTemplate? That would only work if your MongoCollection happened to be a MongoTemplate subclass. To me this looks like a bug.
Class MongoCollection is not a Spring component but a third-party class. Spring AOP can only intercept Spring component calls by means of creating dynamic proxies for those components and adding aspect interceptors to said proxies. so no matter how correct or incorrect your pointcut, it should never trigger.
What you can do instead is switch from Spring AOP to full-blown AspectJ. The standard way to do this is to activate AspectJ load-time weaving (LTW).

Javers ENTITY_INSTANCE_WITH_NULL_ID when using 2 databases

I have this exception "ENTITY_INSTANCE_WITH_NULL_ID" when I store data in Postgres (using JPA Lazy Load) and I store javers in MongoDB
Spring Boot: 1.4.0.RELEASE
Sprig Data JPA: 1.4.0.RELEASE
Javers: 2.3.0
I debugged and saw that ID is null if object is a lazy object:
org.javers.core.metamodel.type.EntityType:88 "Object cdoId = getIdProperty().get(instance);"
When you commit an object to JaVers, its previous version is loaded from JaversRepository and compared with the current version (that version you have just passed to commit() method).
In this case JaVers finds the previous version using GlobalId query so TypeName + entity ID.
That's why ID can't be null for Entities.
There are two possibilities:
If null ID is normal in this class (according to your domain model) you should map it as ValueObject in JaVers.
If you are using Hibernate, there is common problem with lazy loading proxies. For certain queries, Hibernate doesn't return your real domain objects but dynamic proxy objects which are essentially empty (hence null ID). This technique maybe looks smart but makes your objects garbage until they are initialized by Hibernate.
JaVers provides HibernateUnproxyObjectAccessHook which does the cleaning: initializing and un-proxying of your domain objects.
JaversBuilder.javers().withObjectAccessHook(
new HibernateUnproxyObjectAccessHook()).build()
This hook is enabled by default in javers-spring-boot-starter-sql but not in javers-spring-boot-starter-mongo. If you are using Mongo starter, create a JaVers bean on your own, with the hook enabled, see JaversMongoAutoConfiguration.
I solved the problem by making my own access hook and added to the to the Javers with
.withObjectAccessHook(new EntityAccessHook()).build()
public class EntityAccessHook<T> extends HibernateUnproxyObjectAccessHook<T> {
#Override
public Optional<ObjectAccessProxy<T>> createAccessor(T entity) {
Optional<ObjectAccessProxy<T>> accessProxy = super.createAccessor(entity);
if (accessProxy.isEmpty() && entity instanceof AbstractUuidEntity) {
return fromEntityInitializer((AbstractUuidEntity) entity);
}
return accessProxy;
}
private Optional<ObjectAccessProxy<T>> fromEntityInitializer(
AbstractUuidEntity abstractUuidEntity) {
return Optional.of(
new ObjectAccessProxy(
() -> abstractUuidEntity,
abstractUuidEntity.getClass(),
abstractUuidEntity.getId() == null ? UUID.randomUUID() : abstractUuidEntity.getId()));
}
}

Configure JAX-RS base URI programmatically per deployment

Im trying to use CDI extensions to discover JAX-RS resources at runtime and automatically publish them under different base URIs in a Java SE environment. Applications should not need to extend javax.ws.rs.core.Application themselves if possible.
I have read RestEasy documentation and javadoc but failed to find any obvious way to modify the #ApplicationPath at runtime.
One idea that im exploring is to try generate javax.ws.rs.core.Application and set the #ApplicationPath base URI programmatically, maybe by using an AnnotatedType CDI extension, and publish that as a * org.jboss.resteasy.spi.ResteasyDeployment`.
Are there other/better ways to do this?
EDIT:
Trying CDI extension event ProcessAnnotatedType to change #javax.ws.rs.Path of JAX-RS resources.
<X> void process(#Observes ProcessAnnotatedType<X> pat) {
if (!pat.getAnnotatedType().isAnnotationPresent(javax.ws.rs.Path.class)) {
return;
}
final AnnotatedType<X> org = pat.getAnnotatedType();
AnnotatedType<X> wrapped = new AnnotatedType<X>() {
#Override
public <T extends Annotation> T getAnnotation(final Class<T> annotation) {
if (javax.ws.rs.Path.class.equals(annotation)) {
class PathLiteral extends AnnotationLiteral<javax.ws.rs.Path> implements javax.ws.rs.Path {
#Override
public String value() {
return "change_me/" + (javax.ws.rs.Path) org.getAnnotation(annotation);
}
}
return (T) new PathLiteral();
} else {
return org.getAnnotation(annotation);
}
}
pat.setAnnotatedType(wrapped);
}
... then after bootstrap, constructing the bean using javax.enterprise.inject.spi.BeanManager was expecting the following code to print "change_me/...."
Set<Bean<?>> beans = beanManager.getBeans(jaxrsClass);
for (Bean<?> bean : beans) {
CreationalContext cc = bm.createCreationalContext(bean);
Object jaxrs = bean.create(cc);
Path p = jaxrs.getClass().getAnnotation(Path.class);
System.out.println(p.value());
}
... but this does not work. javax.ws.rs.Path is unchanged for JAX-RS resource 'jaxrsClass'.
What is wrong?
I doubt this can be done in a reliable way. It probably all comes down to which happens first: the CDI bootstrap or JAX-RS, of course in the future or in other application servers it could all be done in parallel.
It's certainly a cool idea though. What have they said on the RestEasy forums?
We are already using such an approach.
We are using the feature to use Subresource locators and take the power of guice.
At the startup we are scanning the classpath for all resources annotated with #Path. After that we are extracting the path and binding the resources with the help of Names/#Named. So the resources can later be injected with the help of the name.
bind(..).annotatedWith(Names.named("path")).to(..)
The next step is that you need a resource with a subresource locator.
#Path("{name}")
public Object find(#PathParam("name") name){
return injector.getInstance(..);
}
You could use this approach to bind them at runtime and also to change the original annotated path.

How to inject persistence context to different data source programmatically

In standard EJB 3, when injecting entity manager, persistence unit (which refers to datasource) is hardcoded into annotation: (or alternatively xml file)
#PersistenceContext(unitName = "myunit")
private EntityManager entityManager;
Is there a way to use an entity manager but to select data source by name at runtime?
Using EclipseLink, You can set a DataSource configured in your app server.
import org.eclipse.persistence.config.PersistenceUnitProperties;
...
....
Map props = new HashMap();
props.put(PersistenceUnitProperties.JTA_DATASOURCE, "dataSource");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("UNIT_NAME", props);
EntityManager em = emf.createEntityManager();
PU_NAME refers to the name used in the file persistence.xml
dataSource refers name used in the app server for the jdbc Resource as "jdbc/sample"
Configure required data-sources & persistent-units in persistence.xml.
<persistence-unit name="UNIT_NAME" transaction-type="JTA">
<provider>PERSISTENCE_PROVIDER</provider>
<jta-data-source>java:DATA_SOURCE_NAME</jta-data-source>
</persistence-unit>
-- other units
Now at runtime you can build entity-manager for the required persistence-unit. Create separate persistence-units for each data-source.
//---
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager em = emf.createEntityManager();
//---
Else you can also build factory by providing a map of properties like db-url, userName etc.
createEntityManagerFactory(persistenceUnitName,propertiesMap);
This will create and return an EntityManagerFactory for the named persistence unit using the given properties. Therefore you can change the properties at runtime accordingly.
It is possible! I've done it and it works under JBoss AS and WebSphere.
I use a custom persistence provider which extends org.hibernate.ejb.HibernatePersistence (you need to modify a private static final field to set your persistence provider name into org.hibernate.ejb3.Ejb3Configuration.IMPLEMENTATION_NAME: this is a kind of black magic but it works). Make sure your persistence.xml's persistence units have the custom provider set in the <provider> tag and your custom provider is registered in META-INF/services/javax.persistence.spi.PersistenceProvider.
My provider overrides the createContainerEntityManagerFactory(PersistenceUnitInfo,Map) method called the Java EE container as such (for JTA datasource but it would be easy to do it also for non JTA datasource):
#Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {
// load the DataSource
String newDataSourceName = ...; // any name you want
DataSource ds = (DataSource)(new InitialContext().lookup(newDataSourceName));
// modify the datasource
try {
try {
// JBoss implementation (any maybe other Java EE vendors except IBM WebSphere)
Method m = info.getClass().getDeclaredMethod("setJtaDataSource", DataSource.class);
m.setAccessible(true);
m.invoke(info, ds);
} catch (NoSuchMethodException e) {
// method does not exist (WebSphere?) => try the WebSphere way
// set the datasource name
Method m = info.getClass().getDeclaredMethod("setJtaDataSource", String.class);
m.setAccessible(true);
m.invoke(info, newDataSourceName);
// do the lookup
Method m2 = info.getClass().getDeclaredMethod("lookupJtaDataSource", String.class);
m2.setAccessible(true);
m2.invoke(info);
}
} catch (Throwable e) {
throw new RuntimeException("could not change DataSource for "+info.getClass().getName());
}
// delegate the EMF creation
return new HibernatePersistence().createContainerEntityManaferFactory(info, map);
}
The createEntityManagerFactory(String,Map) also overriden but is much simpler:
#Override
public EntityManagerFactory createEntityManagerFactory(String persistenceUnitInfo, Map map) {
// change the datasource name
String newDataSourceName = ...; // any name you want
if (map==null) map = new HashMap();
map.put(HibernatePersistence.JTA_DATASOURCE, newDataSourceName);
// delegate the EMF creation
return new HibernatePersistence().createEntityManaferFactory(persistenceUnitInfo, map);
}
Note that I only wrote here the core code. In fact, my persistence provider has a lot of other functionalities:
check that the DataSource is up and running
set the transaction manager for JBoss or WebSphere
cache the EMF for lower memory usage
reconfigure the Hibernate query plan cache for smaller memory usage
register JMX bean (to allow more than one EAR to get the same persistence unit name)
I want to indicate that the usage of
Persistence.createEntityManagerFactory(persistenceUnitName)
recommended in the answer of Nayan is classified by the JPA Specification (JSR 317) as follows (footnote in "9.2 Bootstrapping in Java SE Environments"):
"Use of these Java SE bootstrapping APIs may be supported in Java EE containers; however, support for such use is not required."
So this isn't a standard solution for EJB. Anyway, I can confirm that this is working in EclipseLink.
Note: I'm not yet allowed to post this as a comment.