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

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

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * 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-2017] [Payara Foundation and/or its affiliates]

package org.glassfish.persistence.jpa;

import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
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 javax.inject.Inject;
import org.jvnet.hk2.annotations.Service;
import org.glassfish.hk2.api.PostConstruct;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;


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

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy