org.glassfish.persistence.jpa.JPADeployer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2008-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]
package org.glassfish.persistence.jpa;
import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
import com.sun.appserv.connectors.internal.api.ConnectorRuntimeException;
import com.sun.enterprise.admin.cli.Environment;
import com.sun.enterprise.admin.cli.ProgramOptions;
import com.sun.enterprise.admin.cli.remote.RemoteCLICommand;
import com.sun.enterprise.admin.report.PlainTextActionReporter;
import com.sun.enterprise.admin.util.RemoteInstanceCommandHelper;
import com.sun.enterprise.deployment.*;
import com.sun.enterprise.deployment.util.DOLUtils;
import com.sun.enterprise.module.bootstrap.StartupContext;
import com.sun.enterprise.util.ExceptionUtil;
import com.sun.logging.LogDomains;
import org.glassfish.api.deployment.DeployCommandParameters;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.Events;
import org.glassfish.deployment.common.RootDeploymentDescriptor;
import org.glassfish.internal.data.ApplicationInfo;
import org.glassfish.internal.data.ApplicationRegistry;
import org.glassfish.internal.deployment.Deployment;
import org.glassfish.internal.deployment.ExtendedDeploymentContext;
import org.glassfish.server.ServerEnvironmentImpl;
import org.glassfish.api.deployment.DeploymentContext;
import org.glassfish.api.deployment.MetaData;
import org.glassfish.api.deployment.OpsParams;
import org.glassfish.deployment.common.SimpleDeployer;
import org.glassfish.deployment.common.DeploymentException;
import org.glassfish.persistence.common.Java2DBProcessorHelper;
import jakarta.inject.Inject;
import org.jvnet.hk2.annotations.Service;
import org.glassfish.hk2.api.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.CommandException;
import org.glassfish.api.admin.CommandRunner;
import org.glassfish.api.admin.ParameterMap;
import org.glassfish.deployment.common.DeploymentProperties;
import org.glassfish.internal.api.Globals;
import org.glassfish.internal.api.InternalSystemAdministrator;
/**
* Deployer for JPA applications
* @author Mitesh Meswani
*/
@Service
public class JPADeployer extends SimpleDeployer implements PostConstruct, EventListener {
@Inject
private ConnectorRuntime connectorRuntime;
@Inject
private ServerEnvironmentImpl serverEnvironment;
@Inject
private volatile StartupContext sc = null;
@Inject
private Events events;
@Inject
private ApplicationRegistry applicationRegistry;
private static Logger logger = LogDomains.getLogger(PersistenceUnitLoader.class, LogDomains.PERSISTENCE_LOGGER + ".jpadeployer");
/** Key used to get/put emflists in transientAppMetadata */
private static final String EMF_KEY = EntityManagerFactory.class.toString();
@Override public MetaData getMetaData() {
return new MetaData(true /*invalidateCL */ ,
null /* provides */,
new Class[] {Application.class} /* requires Application from dol */);
}
protected void generateArtifacts(DeploymentContext dc) throws DeploymentException {
// Noting to generate yet!!
}
protected void cleanArtifacts(DeploymentContext dc) throws DeploymentException {
// Drop tables if needed on undeploy.
OpsParams params = dc.getCommandParameters(OpsParams.class);
if (params.origin.isUndeploy() && isDas()) {
boolean hasScopedResource = false;
String appName = params.name();
ApplicationInfo appInfo = applicationRegistry.get(appName);
Application application = appInfo.getMetaData(Application.class);
Set bundles = application.getBundleDescriptors();
// Iterate through all the bundles of the app and collect pu references in referencedPus
for (BundleDescriptor bundle : bundles) {
Collection extends PersistenceUnitDescriptor> pusReferencedFromBundle = bundle.findReferencedPUs();
for(PersistenceUnitDescriptor pud : pusReferencedFromBundle) {
hasScopedResource = hasScopedResource(pud);
if(hasScopedResource) {
break;
}
}
}
// if there are scoped resources, deploy them so that they are accessible for Java2DB to
// delete tables.
if(hasScopedResource){
connectorRuntime.registerDataSourceDefinitions(application);
}
Java2DBProcessorHelper helper = new Java2DBProcessorHelper(dc);
helper.init();
helper.createOrDropTablesInDB(false, "JPA"); // NOI18N
//if there are scoped resources, undeploy them.
if(hasScopedResource){
connectorRuntime.unRegisterDataSourceDefinitions(application);
}
}
}
/**
* @inheritDoc
*/
public V loadMetaData(Class type, DeploymentContext context) {
return null;
}
/**
* EMFs for refered pus are created and stored in JPAApplication instance.
* The JPAApplication instance is stored in given DeploymentContext to be retrieved by load
*/
@Override public boolean prepare(DeploymentContext context) {
boolean prepared = super.prepare(context);
if(prepared) {
if(isEMFCreationRequired(context)) {
createEMFs(context);
}
}
return prepared;
}
/**
* CreateEMFs and save them in persistence
* @param context
*/
private void createEMFs(DeploymentContext context) {
Application application = context.getModuleMetaData(Application.class);
Set bundles = application.getBundleDescriptors();
// Iterate through all the bundles for the app and collect pu references in referencedPus
boolean hasScopedResource = false;
final List referencedPus = new ArrayList();
for (BundleDescriptor bundle : bundles) {
Collection extends PersistenceUnitDescriptor> pusReferencedFromBundle = bundle.findReferencedPUs();
for(PersistenceUnitDescriptor pud : pusReferencedFromBundle) {
referencedPus.add(pud);
if( hasScopedResource(pud) ) {
hasScopedResource = true;
}
}
}
if (hasScopedResource) {
// Scoped resources are registered by connector runtime after prepare(). That is too late for JPA
// This is a hack to initialize connectorRuntime for scoped resources
connectorRuntime.registerDataSourceDefinitions(application);
}
//Iterate through all the PUDs for this bundle and if it is referenced, load the corresponding pu
PersistenceUnitDescriptorIterator pudIterator = new PersistenceUnitDescriptorIterator() {
@Override void visitPUD(PersistenceUnitDescriptor pud, DeploymentContext context) {
if(referencedPus.contains(pud)) {
boolean isDas = isDas();
if (isDas && !isTargetDas(context.getCommandParameters(DeployCommandParameters.class))) {
DeployCommandParameters deployParams = context.getCommandParameters(DeployCommandParameters.class);
//If on DAS and not generating schema for remotes then return here
String jpaScemaGeneration = pud.getProperties().getProperty("jakarta.persistence.schema-generation.database.action", "none").toLowerCase();
String eclipselinkSchemaGeneration = pud.getProperties().getProperty("eclipselink.ddl-generation", "none").toLowerCase();
if ("none".equals(jpaScemaGeneration) && "none".equals(eclipselinkSchemaGeneration)) {
return;
} else {
InternalSystemAdministrator kernelIdentity = Globals.getDefaultHabitat().getService(InternalSystemAdministrator.class);
CommandRunner commandRunner = Globals.getDefaultHabitat().getService(CommandRunner.class);
CommandRunner.CommandInvocation getTranslatedValueCommand = commandRunner.getCommandInvocation("_get-translated-config-value", new PlainTextActionReporter(), kernelIdentity.getSubject());
ParameterMap params = new ParameterMap();
params.add("propertyName", pud.getJtaDataSource());
params.add("target", deployParams.target);
getTranslatedValueCommand.parameters(params);
getTranslatedValueCommand.execute();
ActionReport report = getTranslatedValueCommand.report();
if (report.hasSuccesses() && report.getSubActionsReport().size() == 1) {
ActionReport subReport = report.getSubActionsReport().get(0);
String value = subReport.getMessage().replace(deployParams.target + ":", "");
pud.setJtaDataSource(value.trim());
} else {
logger.log(Level.SEVERE, report.getMessage(), report.getFailureCause());
}
}
}
// While running in embedded mode, it is not possible to guarantee that entity classes are not loaded by the app classloader before transformers are installed
// If that happens, weaving will not take place and EclipseLink will throw up. Provide users an option to disable weaving by passing the flag.
// Note that we enable weaving if not explicitly disabled by user
boolean weavingEnabled = Boolean.valueOf(sc.getArguments().getProperty("org.glassfish.persistence.embedded.weaving.enabled", "true"));
ProviderContainerContractInfo providerContainerContractInfo = weavingEnabled ?
new ServerProviderContainerContractInfo(context, connectorRuntime, isDas) :
new EmbeddedProviderContainerContractInfo(context, connectorRuntime, isDas);
try {
((ExtendedDeploymentContext) context).prepareScratchDirs();
} catch (IOException e) {
// There is no way to recover if we are not able to create the scratch dirs. Just rethrow the exception.
throw new RuntimeException(e);
}
try {
PersistenceUnitLoader puLoader = new PersistenceUnitLoader(pud, providerContainerContractInfo);
// Store the puLoader in context. It is retrieved to execute java2db and to
// store the loaded emfs in a JPAApplicationContainer object for cleanup
context.addTransientAppMetaData(getUniquePuIdentifier(pud), puLoader);
} catch (Exception e) {
DeployCommandParameters dcp = context.getCommandParameters(DeployCommandParameters.class);
if (dcp.isSkipDSFailure() && ExceptionUtil.isDSFailure(e)) {
logger.log(Level.WARNING, "Resource communication failure exception skipped while loading the pu " + pud.getName(), e);
} else {
if (e.getCause() instanceof ConnectorRuntimeException) {
logger.log(Level.SEVERE, "{0} is not a valid data source. If you are using variable replacement then"
+ "ensure that is available on the DAS.");
}
throw e;
}
}
}
}
};
pudIterator.iteratePUDs(context);
}
/**
* @return true if given pud
is using scoped resource
*/
private boolean hasScopedResource(PersistenceUnitDescriptor pud) {
boolean hasScopedResource = false;
String jtaDataSource = pud.getJtaDataSource();
if(jtaDataSource != null && jtaDataSource.startsWith("java:")){
hasScopedResource = true;
}
return hasScopedResource;
}
/**
* @param context
* @return true if emf creation is required false otherwise
*/
private boolean isEMFCreationRequired(DeploymentContext context) {
/*
Here are various use cases that needs to be handled.
This method handles EMF creation part, APPLICATION_PREPARED event handle handles java2db and closing of emf
To summarize,
-Unconditionally create EMFs on DAS for java2db if it is deploy. We will close this EMF in APPLICATION_PREPARED after java2db if (target!= DAS || enable=false)
-We will not create EMFs on instance if application is not enabled
------------------------------------------------------------------------------------
Scenario Expected Behavior
------------------------------------------------------------------------------------
deploy --target=server --enabled=true. DAS(EMF created, java2db, EMF remains open)
-restart DAS(EMF created, EMF remains open)
-undeploy DAS(EMF closed. Drop tables)
-create-application-ref instance1 DAS(No action)
INSTANCE1(EMF created)
deploy --target=server --enabled=false. DAS(EMF created,java2db, EMF closed in APPLICATION_PREPARED)
-restart DAS(No EMF created)
-undeploy DAS(No EMF to close, Drop tables)
-enable DAS(EMF created)
-undelpoy DAS(EMF closed, Drop tables)
-create-application-ref instance1 DAS(No action)
INSTANCE1(EMF created)
deploy --target=instance1 --enabled=true DAS(EMF created, java2db, EMF closed in APPLICATION_PREPARED)
INSTANCE1(EMF created)
-create-application-ref instance2 INSTANCE2(EMF created)
-restart DAS(No EMF created)
INSTANCE1(EMF created)
INSTANCE2(EMF created)
-undeploy DAS(No EMF to close, Drop tables)
INSTANCE1(EMF closed)
-create-application-ref server DAS(EMF created)
-delete-application-ref server DAS(EMF closed)
undeploy INSTANCE1(EMF closed)
deploy --target=instance --enabled=false. DAS(EMF created, java2db, EMF closed in APPLICATION_PREPARED)
INSTANCE1(No EMF created)
-create-application-ref instance2 DAS(No action)
INSTANCE2(No Action)
-restart DAS(No EMF created)
INSTANCE1(No EMF created)
INSTANCE2(No EMF created)
-undeploy DAS(No EMF to close, Drop tables)
INSTANCE1(No EMF to close)
INSTANCE2(No EMF to close)
-enable --target=instance1 DAS(No EMF created)
INSTANCE1(EMF created)
*/
boolean createEMFs = false;
DeployCommandParameters deployCommandParameters = context.getCommandParameters(DeployCommandParameters.class);
boolean deploy = deployCommandParameters.origin.isDeploy();
boolean enabled = deployCommandParameters.enabled;
boolean isDas = isDas();
if(logger.isLoggable(Level.FINER)) {
logger.finer("isEMFCreationRequired(): deploy: " + deploy + " enabled: " + enabled + " isDas: " + isDas);
}
if(isDas) {
if(deploy) {
createEMFs = true; // Always create emfs on DAS while deploying to take care of java2db and PU validation on deploy
} else {
//We reach here for (!deploy && das) => server restart or enabling a disabled app on DAS
boolean isTargetDas = isTargetDas(deployCommandParameters);
if(logger.isLoggable(Level.FINER)) {
logger.finer("isEMFCreationRequired(): isTargetDas: " + isTargetDas);
}
if(enabled && isTargetDas) {
createEMFs = true;
}
}
} else { //!das => on an instance
if(enabled) {
createEMFs = true;
}
}
if(logger.isLoggable(Level.FINER)) {
logger.finer("isEMFCreationRequired(): returning createEMFs:" + createEMFs);
}
return createEMFs;
}
private static boolean isTargetDas(DeployCommandParameters deployCommandParameters) {
return "server".equals(deployCommandParameters.target); // TODO discuss with Hong. This comparison should be encapsulated somewhere
}
/**
* @inheritDoc
*/
//@Override
public JPApplicationContainer load(JPAContainer container, DeploymentContext context) {
return new JPApplicationContainer();
}
/**
* Returns unique identifier for this pu within application
* @param pud The given pu
* @return Absolute pu root + pu name
*/
private static String getUniquePuIdentifier(PersistenceUnitDescriptor pud) {
return pud.getAbsolutePuRoot() + pud.getName();
}
private boolean isDas() {
return serverEnvironment.isDas() || serverEnvironment.isEmbedded();
}
@Override
public void postConstruct() {
events.register(this);
}
@Override
public void event(Event event) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("JpaDeployer.event():" + event.name());
}
if (event.is(Deployment.APPLICATION_PREPARED) ) {
ExtendedDeploymentContext context = (ExtendedDeploymentContext)event.hook();
DeployCommandParameters deployCommandParameters = context.getCommandParameters(DeployCommandParameters.class);
if(logger.isLoggable(Level.FINE)) {
logger.fine("JpaDeployer.event(): Handling APPLICATION_PREPARED origin is:" + deployCommandParameters.origin);
}
// When create-application-ref is called for an already deployed app, APPLICATION_PREPARED will be sent on DAS
// Obviously there is no new emf created for this event and we need not do java2db also. Ignore the event
// However, if target for create-application-ref is DAS => the app was deployed on other instance but now
// an application-ref is being created on DAS. Process the app
if(!deployCommandParameters.origin.isCreateAppRef() || isTargetDas(deployCommandParameters)) {
Map deploymentContexts = context.getModuleDeploymentContexts();
for (DeploymentContext deploymentContext : deploymentContexts.values()) {
//bundle level pus
iterateInitializedPUsAtApplicationPrepare(deploymentContext);
}
//app level pus
iterateInitializedPUsAtApplicationPrepare(context);
}
} else if(event.is(Deployment.APPLICATION_DISABLED)) {
logger.fine("JpaDeployer.event(): APPLICATION_DISABLED");
// APPLICATION_DISABLED will be generated when an app is disabled/undeployed/appserver goes down.
//close all the emfs created for this app
ApplicationInfo appInfo = (ApplicationInfo) event.hook();
closeEMFs(appInfo);
}
}
private void closeEMFs(ApplicationInfo appInfo) {
//Suppress warning required as there is no way to pass equivalent of List.class to the method
@SuppressWarnings("unchecked") List emfsCreatedForThisApp = appInfo.getTransientAppMetaData(EMF_KEY, List.class);
if(emfsCreatedForThisApp != null) { // Events are always dispatched to all registered listeners. emfsCreatedForThisApp will be null for an app that does not have PUs.
for (EntityManagerFactory entityManagerFactory : emfsCreatedForThisApp) {
entityManagerFactory.close();
}
// We no longer have the emfs in open state clear the list.
// On app enable(after a disable), for a cluster, the deployment framework calls prepare() for instances but not for DAS.
// So on DAS, at a disable, the emfs will be closed and we will not attempt to close emfs when appserver goes down even if the app is re-enabled.
emfsCreatedForThisApp.clear();
}
}
/**
* Does java2db on DAS and saves emfs created during prepare to ApplicationInfo maintained by DOL.
* ApplicationInfo is not available during prepare() so we can not directly use it there.
* @param context
*/
private void iterateInitializedPUsAtApplicationPrepare(final DeploymentContext context) {
final DeployCommandParameters deployCommandParameters = context.getCommandParameters(DeployCommandParameters.class);
String appName = deployCommandParameters.name;
final ApplicationInfo appInfo = applicationRegistry.get(appName);
//iterate through all the PersistenceUnitDescriptor for this bundle.
PersistenceUnitDescriptorIterator pudIterator = new PersistenceUnitDescriptorIterator() {
@Override void visitPUD(PersistenceUnitDescriptor pud, DeploymentContext context) {
//PersistenceUnitsDescriptor corresponds to persistence.xml. A bundle can only have one persitence.xml except
// when the bundle is an application which can have multiple persitence.xml under jars in root of ear and lib.
PersistenceUnitLoader puLoader = context.getTransientAppMetaData(getUniquePuIdentifier(pud), PersistenceUnitLoader.class);
if (puLoader != null) { // We have initialized PU
boolean saveEMF = true;
if(isDas()) { //We do validation and execute Java2DB only on DAS
if(deployCommandParameters.origin.isDeploy()) { //APPLICATION_PREPARED will be called for create-application-ref also. We should perform java2db only on first deploy
//Create EM to trigger validation on PU
EntityManagerFactory emf = puLoader.getEMF();
EntityManager em = null;
try {
// Create EM to trigger any validations that are lazily performed by the provider
// EM creation also triggers DDL generation by provider.
em = emf.createEntityManager();
} catch (PersistenceException e) {
// Exception indicates something went wrong while performing validation. Clean up and rethrow to fail deployment
emf.close();
DeployCommandParameters dcp = context.getCommandParameters(DeployCommandParameters.class);
if (dcp.isSkipDSFailure() && ExceptionUtil.isDSFailure(e)) {
logger.log(Level.WARNING, "Resource communication failure exception skipped while loading the pu " + pud.getName(), e);
} else {
throw new DeploymentException(e); // Need to wrap exception in DeploymentException else deployment will not fail !!
}
} finally {
if (em != null) {
em.close();
}
}
puLoader.doJava2DB();
boolean enabled = deployCommandParameters.enabled;
boolean isTargetDas = isTargetDas(deployCommandParameters);
if(logger.isLoggable(Level.FINER)) {
logger.finer("iterateInitializedPUsAtApplicationPrepare(): enabled: " + enabled + " isTargetDas: " + isTargetDas);
}
if(!isTargetDas || !enabled) {
// we are on DAS but target != das or app is not enabled on das => The EMF was just created for Java2Db. Close it.
puLoader.getEMF().close();
saveEMF = false; // Do not save EMF. We have already closed it
}
}
}
if(saveEMF) {
// Save emf in ApplicationInfo so that it can be retrieved and closed for cleanup
@SuppressWarnings("unchecked") //Suppress warning required as there is no way to pass equivalent of List.class to the method
List emfsCreatedForThisApp = appInfo.getTransientAppMetaData(EMF_KEY, List.class );
if(emfsCreatedForThisApp == null) {
//First EMF for this app, initialize
emfsCreatedForThisApp = new ArrayList();
appInfo.addTransientAppMetaData(EMF_KEY, emfsCreatedForThisApp);
}
emfsCreatedForThisApp.add(puLoader.getEMF());
} // if (saveEMF)
} // if(puLoader != null)
}
};
pudIterator.iteratePUDs(context);
}
/**
* Helper class to centralize the code for loop that iterates through all the PersistenceUnitDescriptor for a given DeploymentContext (and hence the corresponding bundle)
*/
private static abstract class PersistenceUnitDescriptorIterator {
/**
* Iterate through all the PersistenceUnitDescriptors for the given context (and hence corresponding bundle) and call visitPUD for each of them
* @param context
*/
void iteratePUDs(DeploymentContext context) {
RootDeploymentDescriptor currentBundle = DOLUtils.getCurrentBundleForContext(context);
if (currentBundle != null) { // it can be null for non-JavaEE type of application deployment. e.g., issue 15869
Collection pusDescriptorForThisBundle = currentBundle.getExtensionsDescriptors(PersistenceUnitsDescriptor.class);
for (PersistenceUnitsDescriptor persistenceUnitsDescriptor : pusDescriptorForThisBundle) {
for (PersistenceUnitDescriptor pud : persistenceUnitsDescriptor.getPersistenceUnitDescriptors()) {
visitPUD(pud, context);
}
}
}
}
/**
* Called for each PersistenceUnitDescriptor visited by this iterator.
*/
abstract void visitPUD(PersistenceUnitDescriptor pud, DeploymentContext context);
}
}