All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.persistence.jpa.JPADeployer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2024 Contributors to the Eclipse Foundation.
 * Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.persistence.jpa;

import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.BundleDescriptor;
import com.sun.enterprise.deployment.PersistenceUnitDescriptor;
import com.sun.enterprise.deployment.PersistenceUnitsDescriptor;
import com.sun.enterprise.deployment.util.DOLUtils;
import com.sun.enterprise.module.bootstrap.StartupContext;
import com.sun.logging.LogDomains;

import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.api.deployment.DeployCommandParameters;
import org.glassfish.api.deployment.DeploymentContext;
import org.glassfish.api.deployment.MetaData;
import org.glassfish.api.deployment.OpsParams;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.Events;
import org.glassfish.api.naming.SimpleJndiName;
import org.glassfish.deployment.common.DeploymentException;
import org.glassfish.deployment.common.RootDeploymentDescriptor;
import org.glassfish.deployment.common.SimpleDeployer;
import org.glassfish.hk2.api.PostConstruct;
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.persistence.common.Java2DBProcessorHelper;
import org.glassfish.server.ServerEnvironmentImpl;
import org.jvnet.hk2.annotations.Service;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;
import static org.glassfish.internal.deployment.Deployment.APPLICATION_PREPARED;

/**
 * Deployer for JPA applications
 *
 * @author Mitesh Meswani
 */
@Service
public class JPADeployer extends SimpleDeployer implements PostConstruct, EventListener {

    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();

    @Inject
    private ConnectorRuntime connectorRuntime;

    @Inject
    private ServerEnvironmentImpl serverEnvironment;

    @Inject
    private volatile StartupContext startupContext;

    @Inject
    private Events events;

    @Inject
    private ApplicationRegistry applicationRegistry;

    @Override
    public void postConstruct() {
        events.register(this);
    }

    @Override
    public MetaData getMetaData() {
        return new MetaData(
            true /* invalidateCL */,
            null /* provides */,
            new Class[] { Application.class } /* requires Application from dol */);
    }

    @Override
    protected void generateArtifacts(DeploymentContext dc) throws DeploymentException {
        // Noting to generate yet!!
    }

    @Override
    protected void cleanArtifacts(DeploymentContext deploymentContext) throws DeploymentException {
        // Drop tables if needed on undeploy.
        OpsParams params = deploymentContext.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 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(deploymentContext);
            helper.init();
            helper.createOrDropTablesInDB(false, "JPA");

            // If there are scoped resources, undeploy them.
            if (hasScopedResource) {
                connectorRuntime.unRegisterDataSourceDefinitions(application);
            }
        }
    }

    @Override
    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;
    }

    @Override
    public JPApplicationContainer load(JPAContainer container, DeploymentContext context) {
        return new JPApplicationContainer();
    }

    @Override
    public void event(Event event) {
        if (logger.isLoggable(Level.FINEST)) {
            logger.finest("JpaDeployer.event():" + event.name());
        }

        if (event.is(APPLICATION_PREPARED)) {
            ExtendedDeploymentContext context = (ExtendedDeploymentContext) event.hook();
            DeployCommandParameters deployCommandParameters = context.getCommandParameters(DeployCommandParameters.class);
            if (logger.isLoggable(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
            closeEntityManagerFactories((ApplicationInfo) event.hook());
        }
    }

    /**
     * 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 static boolean isTargetDas(DeployCommandParameters deployCommandParameters) {
        // TODO discuss with Hong. This comparison should be encapsulated somewhere
        return "server".equals(deployCommandParameters.target);
    }

    private boolean isDas() {
        return serverEnvironment.isDas() || serverEnvironment.isEmbedded();
    }

    private void closeEntityManagerFactories(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();
        }
    }

    /**
     * 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 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();

                    // 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
                            .parseBoolean(startupContext.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);
                    }

                    PersistenceUnitLoader persistenceUnitLoader = 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), persistenceUnitLoader);
                }
            }
        };
        pudIterator.iteratePUDs(context);
    }

    /**
     * @return true if given pud is using scoped resource
     */
    private boolean hasScopedResource(PersistenceUnitDescriptor persistenceUnitDescriptor) {
        boolean hasScopedResource = false;
        SimpleJndiName jtaDataSource = persistenceUnitDescriptor.getJtaDataSource();
        if (jtaDataSource != null && jtaDataSource.hasJavaPrefix()) {
            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 createEntityManagerFactories = false;
        DeployCommandParameters deployCommandParameters = context.getCommandParameters(DeployCommandParameters.class);
        boolean deploy = deployCommandParameters.origin.isDeploy();
        boolean enabled = deployCommandParameters.enabled;
        boolean isDas = isDas();

        if (logger.isLoggable(FINER)) {
            logger.finer("isEMFCreationRequired(): deploy: " + deploy + " enabled: " + enabled + " isDas: " + isDas);
        }

        if (isDas) {
            if (deploy) {
                // Always create emfs on DAS while deploying to take care of java2db and PU
                // validation on deploy
                createEntityManagerFactories = true;
            } else {
                // We reach here for (!deploy && das) => server restart or enabling a disabled
                // app on DAS
                boolean isTargetDas = isTargetDas(deployCommandParameters);
                if (logger.isLoggable(FINER)) {
                    logger.finer("isEMFCreationRequired(): isTargetDas: " + isTargetDas);
                }

                if (enabled && isTargetDas) {
                    createEntityManagerFactories = true;
                }
            }
        } else { // !das => on an instance
            if (enabled) {
                createEntityManagerFactories = true;
            }
        }

        if (logger.isLoggable(FINER)) {
            logger.finer("isEMFCreationRequired(): returning createEMFs:" + createEntityManagerFactories);
        }

        return createEntityManagerFactories;
    }

    /**
     * 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 persistenceUnitLoader = context.getTransientAppMetaData(getUniquePuIdentifier(pud), PersistenceUnitLoader.class);
                if (persistenceUnitLoader != null) { // We have initialized PU
                    boolean saveEntityManagerFactory = true;
                    if (isDas()) { // We do validation and execute Java2DB only on DAS

                        // APPLICATION_PREPARED will be called for create-application-ref
                        // also. We should perform java2db only on first deploy
                        if (deployCommandParameters.origin.isDeploy()) {

                            // Create EntityManager to trigger validation on PU
                            EntityManagerFactory entityManagerFactory = persistenceUnitLoader.getEMF();
                            EntityManager entityManager = null;
                            try {
                                // Create EM to trigger any validations that are lazily performed by the
                                // provider
                                // EM creation also triggers DDL generation by provider.
                                entityManager = entityManagerFactory.createEntityManager();
                            } catch (PersistenceException e) {
                                // Exception indicates something went wrong while performing validation. Clean
                                // up and rethrow to fail deployment
                                entityManagerFactory.close();

                                // Need to wrap exception in DeploymentException else deployment will not
                                // fail.
                                throw new DeploymentException(e);
                            } finally {
                                if (entityManager != null) {
                                    entityManager.close();
                                }
                            }

                            persistenceUnitLoader.doJava2DB();

                            boolean enabled = deployCommandParameters.enabled;
                            boolean isTargetDas = isTargetDas(deployCommandParameters);
                            if (logger.isLoggable(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.
                                persistenceUnitLoader.getEMF().close();
                                saveEntityManagerFactory = false; // Do not save EMF. We have already closed it
                            }
                        }
                    }

                    if (saveEntityManagerFactory) {
                        // Save EntityManagerFactory in ApplicationInfo so that it can be retrieved and closed for cleanup

                        // 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) {
                            // First EMF for this app, initialize
                            emfsCreatedForThisApp = new ArrayList<>();
                            appInfo.addTransientAppMetaData(EMF_KEY, emfsCreatedForThisApp);
                        }
                        emfsCreatedForThisApp.add(persistenceUnitLoader.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);

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy