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

com.sun.enterprise.v3.server.DynamicReloader 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-2021] [Payara Foundation and/or its affiliates]

package com.sun.enterprise.v3.server;

import com.sun.enterprise.config.serverbeans.Application;
import com.sun.enterprise.config.serverbeans.Applications;
import com.sun.enterprise.config.serverbeans.ServerTags;
import com.sun.enterprise.v3.admin.CommandRunnerImpl;
import com.sun.enterprise.admin.report.XMLActionReporter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import org.glassfish.api.admin.ParameterMap;
import org.glassfish.api.admin.config.ApplicationName;
import org.glassfish.api.deployment.DeployCommandParameters;
import org.glassfish.deployment.common.DeploymentProperties;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.internal.api.InternalSystemAdministrator;
import org.glassfish.kernel.KernelLoggerInfo;

/**
 * Triggers reloads of deployed applications depending on the presence of and
 * timestamp on a .reload file in the application's top-level directory.
 * 
 * An instance of this class can be reused, its run method invoked repeatedly
 * to check all known apps for their .reload files.  
 * 
 * @author tjquinn
 */
public class DynamicReloader implements Runnable {

    private static final String RELOAD_FILE_NAME = ".reload";
    
    private static class SyncBoolean {
        private boolean b = false;
        
        private SyncBoolean(final boolean initialValue) {
            b = initialValue;
        }
        
        private synchronized void set(final boolean value) {
            b = value;
        }
        
        private synchronized boolean get() {
            return b;
        }
    }
    private final SyncBoolean inProgress;
    
    /** Records info about apps being monitored */
    private Map appReloadInfo;
    
    private AtomicBoolean cancelRequested = new AtomicBoolean(false);
    
    private Applications applications;
    
    private Logger logger = KernelLoggerInfo.getLogger();
    
    private ServiceLocator habitat;
    
    private final Subject kernelSubject;
    
    DynamicReloader(Applications applications, ServiceLocator habitat) throws URISyntaxException {
        this.applications = applications;
        this.habitat = habitat;
        initAppReloadInfo(applications);
        inProgress = new SyncBoolean(false);
        final InternalSystemAdministrator kernelIdentity = habitat.getService(InternalSystemAdministrator.class);
        kernelSubject = kernelIdentity.getSubject();
    }
    
    /**
     * Records reload information about the currently-known applications.
     * 
     * @param applications
     */
    private synchronized void initAppReloadInfo(Applications applications) throws URISyntaxException {
         appReloadInfo = new HashMap();
         logger.fine("[Reloader] Preparing list of apps to monitor:");
         for (ApplicationName m : applications.getModules()) {
             if (m instanceof Application) {
                 Application app = (Application) m;
                 if (Boolean.valueOf(app.getDeployProperties().getProperty
                     (ServerTags.IS_LIFECYCLE))) {
                     // skip lifecycle modules
                     continue;
                 }
                 AppReloadInfo info = new AppReloadInfo(app);
                 appReloadInfo.put(app.getName(), info);
                 logger.fine("[Reloader] Monitoring " + app.getName() + " at " + app.getLocation());
             }
         }
    }
    
    public void run() {
        markInProgress();
        try {
            reloadApps();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            clearInProgress();
        }
    }

    void cancel() {
        cancelRequested.set(true);
    }
    
    void init() {
        cancelRequested.set(false);
    }
    
    private void reloadApps() throws URISyntaxException, IOException {
        List appsToReload = chooseAppsToReload();
        for (AppReloadInfo appInfo : appsToReload) {
            if (cancelRequested.get()) {
                break;
            }
            reloadApp(appInfo);
        }
    }
    
    private synchronized List chooseAppsToReload() throws URISyntaxException {
        List result = new ArrayList();
        
        /*
         * The collectionof AppReloadInfo might not contain entries for all
         * current apps (for example, if an app has been deployed since the
         * previous run of the reloader).  Use the current list of all known
         * apps, and for each of those try to find an AppReloadInfo entry for
         * it.
         */
        Set possiblyUndeployedApps = new HashSet(appReloadInfo.values());
        
        for (ApplicationName m : applications.getModules()) {
            if (m instanceof Application) {
                Application app = (Application) m;
                if (app.getLocation() == null || Boolean.valueOf(app.getDeployProperties().getProperty
                    (ServerTags.IS_LIFECYCLE))) {
                    // skip apps without a location
                    // skip lifecycle modules
                    continue;
                }
                AppReloadInfo reloadInfo = findOrCreateAppReloadInfo(app);
                if (reloadInfo.needsReload()) {
                    logger.fine("[Reloader] Selecting app " + reloadInfo.getApplication().getName() + " to reload");
                    
                    result.add(reloadInfo);
                }
                possiblyUndeployedApps.remove(reloadInfo);
            }
        }
        
        /*
         * Remove any apps from the reload info that are no longer present.
         */
        for (AppReloadInfo info : possiblyUndeployedApps) {
            logger.fine("[Reloader] Removing undeployed app " + info.getApplication().getName() + " from reload info");
            appReloadInfo.remove(info.getApplication().getName());
        }
        

        return result;
    }

    private synchronized AppReloadInfo findOrCreateAppReloadInfo(Application app) throws URISyntaxException {
        AppReloadInfo result = appReloadInfo.get(app.getName());
        if (result == null) {
            logger.fine("[Reloader] Recording info for new app " + app.getName() + " at " + app.getLocation());
            result = new AppReloadInfo(app);
            appReloadInfo.put(app.getName(), result);
        }
        return result;
    }
    
    private void reloadApp(AppReloadInfo appInfo) throws IOException {
        logger.fine("[Reloader] Reloading " + appInfo.getApplication().getName());
        
        /*
         * Prepare a deploy command and invoke it, taking advantage of the
         * DeployCommand's logic to deal with redeploying an existing app.
         * 
         * Note that the redeployinplace internal option tells the undeploy
         * command (which is invoked by the deploy command) to preserve the
         * existing directory, even if the configuration does not indicate that
         * the app is directory-deployed.
         * 
         */
        CommandRunnerImpl commandRunner = habitat.getService(CommandRunnerImpl.class);

        ParameterMap deployParam = new ParameterMap();
        deployParam.set(DeploymentProperties.FORCE, Boolean.TRUE.toString());
        deployParam.set(DeploymentProperties.PATH, appInfo.getApplicationDirectory().getCanonicalPath());
        deployParam.set(DeploymentProperties.NAME, appInfo.getApplication().getName());
        deployParam.set(DeploymentProperties.KEEP_REPOSITORY_DIRECTORY, "true");

        Properties reloadFile = appInfo.readReloadFile();
        boolean hotDeploy = Boolean.parseBoolean(reloadFile.getProperty(DeployCommandParameters.ParameterNames.HOT_DEPLOY));
        if (hotDeploy) {
            deployParam.set(DeployCommandParameters.ParameterNames.HOT_DEPLOY, "true");
            boolean metadataChanged = Boolean.parseBoolean(reloadFile.getProperty(DeployCommandParameters.ParameterNames.METADATA_CHANGED));
            if (metadataChanged) {
                deployParam.set(DeployCommandParameters.ParameterNames.METADATA_CHANGED, "true");
            }
            String sourcesChanged = reloadFile.getProperty(DeployCommandParameters.ParameterNames.SOURCES_CHANGED);
            if (sourcesChanged != null && !sourcesChanged.isEmpty()) {
                deployParam.set(DeployCommandParameters.ParameterNames.SOURCES_CHANGED, sourcesChanged);
            }
        }
        commandRunner.getCommandInvocation("deploy", new XMLActionReporter(), kernelSubject).parameters(deployParam).execute();

        appInfo.recordLoad();
    }
    
    private void markInProgress() {
        inProgress.set(true);
    }

    private void clearInProgress() {
        synchronized(inProgress) {
            inProgress.set(false);
            inProgress.notifyAll();
        }
    }
    
    public void waitUntilIdle() throws InterruptedException {
        synchronized(inProgress) {
            while (inProgress.get()) {
                inProgress.wait();
            }
        }
    }
    
    /**
     * Records information about every application, regardless of whether the
     * app has a .reload file or not.
     * 
     * The latestRecordedLoad time records either the object creation time (which should
     * be about the same as the initial load time of the app during a server 
     * restart or after a deployment) or the time at which an app was reloaded.
     * 
     * Note that this class uses the fact that lastModified of a non-existing
     * file is 0.
     */
    private final static class AppReloadInfo {
        /** points to the .reload file, whether one exists for this app or not */
        private File reloadFile;
        
        private long latestRecordedLoad;
        
        /** application info */
        private Application app;
        
        private File appDir;
        
        private AppReloadInfo(Application app) throws URISyntaxException {
            this.app = app;
            appDir = new File(new URI(app.getLocation()));
            reloadFile = new File(appDir, RELOAD_FILE_NAME);
            recordLoad();
        }
        
        private Application getApplication() {
            return app;
        }
        
        private boolean needsReload() {
            boolean answer = reloadFile.lastModified() > latestRecordedLoad;
            return answer;
        }
        
        private Properties readReloadFile() {
            Properties prop = new Properties();
            try (InputStream istream = new FileInputStream(reloadFile)) {
                prop.load(istream);
            } catch (Exception ex) {
                // skip
            }
            return prop;
        }
        
        private void recordLoad() {
            latestRecordedLoad = System.currentTimeMillis();
        }
        
        private File getApplicationDirectory() {
            return appDir;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy