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

com.sun.enterprise.v3.admin.cluster.ServerSynchronizer Maven / Gradle / Ivy

There is a newer version: 4.1.2.181
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-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.
 */

package com.sun.enterprise.v3.admin.cluster;

import java.io.*;
import java.util.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Logger;
import java.util.logging.Level;

import javax.inject.Inject;

import org.glassfish.api.admin.*;
import org.jvnet.hk2.annotations.Optional;

import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.component.*;

import org.glassfish.api.ActionReport;
import org.glassfish.api.ActionReport.ExitCode;
import org.glassfish.api.admin.config.ApplicationName;
import com.sun.enterprise.config.serverbeans.Applications;
import com.sun.enterprise.config.serverbeans.Application;
import com.sun.enterprise.config.serverbeans.ApplicationRef;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.SecurityService;
import com.sun.enterprise.util.cluster.SyncRequest;
import com.sun.enterprise.util.cluster.SyncRequest.ModTime;
import com.sun.enterprise.security.auth.realm.file.FileRealm;
import com.sun.enterprise.util.LocalStringManagerImpl;
import org.glassfish.deployment.versioning.VersioningUtils;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.PostConstruct;

/**
 * The core server synchronization logic.  Given a request from
 * the client, it fills the payload with the files the client needs.
 *
 * The list of files in the config directory to synchronize is in
 * META-INF/config-files in this module, or in config/config-files
 * in the domain directory.
 *
 * @author Bill Shannon
 */
@Service
@PerLookup
public final class ServerSynchronizer implements PostConstruct {

    @Inject
    private ServerEnvironment env;

    @Inject
    private Domain domain;

    @Inject @Optional
    private Applications applications;

    private static boolean syncArchive = false;

    private URI domainRootUri;  // URI of the domain's root directory

    private Logger logger;

    private static enum SyncLevel { TOP, DIRECTORY, RECURSIVE };

    private final static LocalStringManagerImpl strings =
        new LocalStringManagerImpl(ServerSynchronizer.class);

    @Override
    public void postConstruct() {
        domainRootUri = env.getDomainRoot().toURI();
    }

    /**
     * Handle a single syncrhonization request for the given server
     * by adding the needed files to the payload.
     */
    public void synchronize(Server server, SyncRequest sr,
                            Payload.Outbound payload,
                            ActionReport report, Logger logger) {
        this.logger = logger;
        try {
            if (logger.isLoggable(Level.FINE))
                logger.fine("ServerSynchronizer: synchronization request for " +
                        "server " + server.getName() + ", directory " + sr.dir);
            // handle the request appropriately based on the directory
            if (sr.dir.equals("config"))
                synchronizeConfig(payload, server, sr);
            else if (sr.dir.equals("applications"))
                synchronizeApplications(payload, server, sr);
            else if (sr.dir.equals("lib"))
                synchronizeLib(payload, server, sr);
            else if (sr.dir.equals("docroot"))
                synchronizeDocroot(payload, server, sr);
            else if (sr.dir.equals("config-specific"))
                synchronizeConfigSpecificDir(payload, server, sr);
            else {
                report.setActionExitCode(ExitCode.FAILURE);
                report.setMessage(
                        strings.getLocalString("serversync.unknown.dir",
                            "Unknown directory: {0}", sr.dir));
                return;
            }
            report.setActionExitCode(ExitCode.SUCCESS);
        } catch (URISyntaxException ex) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("ServerSynchronizer: Exception processing request");
                logger.fine(ex.toString());
            }
            report.setActionExitCode(ExitCode.FAILURE);
            report.setMessage(
                    strings.getLocalString("serversync.exception.processing",
                        "ServerSynchronizer: Exception processing request"));
            report.setFailureCause(ex);
        }
    }

    /**
     * Synchronize files in the config directory.
     * If the domain.xml file is up to date, don't worry
     * about any of the other files.
     */
    private void synchronizeConfig(Payload.Outbound payload, Server server,
                                    SyncRequest sr) throws URISyntaxException {
        logger.finer("ServerSynchronizer: synchronize config");
        // find the domain.xml entry
        ModTime domainXmlMT = null;
        for (ModTime mt : sr.files) {
            if (mt.name.equals("domain.xml")) {
                domainXmlMT = mt;
                break;
            }
        }
        if (domainXmlMT == null)        // couldn't find it, fake it
            domainXmlMT = new ModTime("domain.xml", 0);

        File configDir = env.getConfigDirPath();
        if (!syncFile(domainRootUri, configDir, domainXmlMT, payload)) {
            logger.fine("ServerSynchronizer: domain.xml HAS NOT CHANGED, " +
                        "thus no files will be synchronized");
            return;
        }

        // get the set of all the config files we need to consider
        Set configFileSet = getConfigFileNames();
        configFileSet.remove("domain.xml");     // already handled it

        // add the list of file realm files
        getRealmFileNames(server, configFileSet);

        for (ModTime mt : sr.files) {
            if (mt.name.equals("domain.xml"))   // did domain.xml above
                continue;
            if (configFileSet.contains(mt.name)) {
                // if client has file, remove it from set
                configFileSet.remove(mt.name);
                syncFile(domainRootUri, configDir, mt, payload);
            } else
                removeFile(domainRootUri, configDir, mt, payload);
        }

        // now do all the remaining files the client doesn't have
        for (String name : configFileSet)
            syncFile(domainRootUri, configDir, new ModTime(name, 0), payload);
    }

    /**
     * Return the names of the config files we need to consider.
     * Names are all relative to the config directory.
     */
    private Set getConfigFileNames() {
        Set files = new LinkedHashSet();
        BufferedReader in = null;
        try {
            File configDir = env.getConfigDirPath();
            File f = new File(configDir, "config-files");
            if (f.exists())
                in = new BufferedReader(new InputStreamReader(
                                                new FileInputStream(f)));
            else {
                InputStream res =
                    getClass().getResourceAsStream("/META-INF/config-files");
                if (res != null)
                    in = new BufferedReader(new InputStreamReader(res));
                else
                    logger.severe("ServerSynchronizer: can't find list of " +
                                "config files to synchronize!");
            }
            String line;
            if (in != null) {
                while ((line = in.readLine()) != null) {
                    if (line.startsWith("#"))   // ignore comment lines
                        continue;
                    line = line.trim();
                    if (line.length() == 0)     // ignore blank lines
                        continue;
                    files.add(line);
                }
            }
        } catch (IOException ex) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine(
                    "ServerSynchronizer: IOException in getConfigFileNames");
                logger.fine(ex.toString());
            }
        } finally {
            try {
                if (in != null)
                    in.close();
            } catch (IOException cex) {
            }
        }
        return files;
    }

    /**
     * Get the names of any realm files in the config directory
     * and add them to the set of file names.  This will normally
     * find at least the "admin-keyfile" and "keyfile" files.
     */
    private void getRealmFileNames(Server server, Set files) {
        File configDir = env.getConfigDirPath();
        URI configURI = configDir.toURI();
        Config config = domain.getConfigNamed(server.getConfigRef());
        for (String file : FileRealm.getRealmFileNames(config)) {
            File rfile = new File(file);
            if (!rfile.exists())        // skip if file doesn't exist
                continue;
            URI f = configURI.relativize(rfile.toURI());
            if (!f.isAbsolute())        // if file is in config dir, add it
                files.add(f.toString());
        }
    }

    /**
     * Sync an individual file.  Return true if the file changed.
     * The file is named by mt.name, relative to base.  The name
     * used in the response will be relative to root.  In case the
     * file is a directory, tell the payload to include it recursively,
     * and replace the entire contents of the directory in case any
     * files were removed.
     */
    private boolean syncFile(URI root, File base, ModTime mt,
                            Payload.Outbound payload)
                            throws URISyntaxException {
        File f = fileOf(base, mt.name);
        if (!f.exists())
            return false;
        if (mt.time != 0 && f.lastModified() == mt.time)
            return false;     // success, nothing to do
        if (logger.isLoggable(Level.FINEST))
            logger.finest("ServerSynchronizer: file " + mt.name +
                            " out of date, time " + f.lastModified());
        try {
            if (logger.isLoggable(Level.FINE))
                logger.fine("ServerSynchronizer: sending file " + f +
                                (mt.time == 0 ?
                                    " because it doesn't exist on the instance":
                                    " because it was out of date"));
            payload.requestFileReplacement("application/octet-stream",
                root.relativize(f.toURI()),
                "configChange", null, f, true);
        } catch (IOException ioex) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("ServerSynchronizer: IOException attaching file: " +
                                                                            f);
                logger.fine(ioex.toString());
            }
        }
        return true;
    }

    /**
     * Send a request to the client to remove the specified file.
     * The file is named by mt.name, relative to base.  The name
     * used in the response will be relative to root.
     */
    private void removeFile(URI root, File base, ModTime mt,
                            Payload.Outbound payload)
                            throws URISyntaxException {
        File f = fileOf(base, mt.name);
        if (logger.isLoggable(Level.FINEST))
            logger.finest("ServerSynchronizer: file " + mt.name +
                            " removed from client");
        try {
            if (logger.isLoggable(Level.FINE))
                logger.fine("ServerSynchronizer: removing file " + f +
                            " because it does not exist on the DAS");
            payload.requestFileRemoval(
                root.relativize(f.toURI()),
                "configChange", null);
        } catch (IOException ioex) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("ServerSynchronizer: IOException removing file: " +
                                                                            f);
                logger.fine(ioex.toString());
            }
        }
    }

    /**
     * Synchronize all the applications in the applications directory.
     * We use the mod time of the application directory to decide if
     * the application has changed.  If it has changed, we also send
     * any of the generated content.
     */
    private void synchronizeApplications(Payload.Outbound payload,
                                    Server server, SyncRequest sr)
                                    throws URISyntaxException {
        if (logger.isLoggable(Level.FINER))
            logger.finer("ServerSynchronizer: " +
                            "synchronize application instance " + sr.instance);
        Map apps = getApps(server);
        File appsDir = env.getApplicationRepositoryPath();

        for (ModTime mt : sr.files) {
            if (apps.containsKey(mt.name)) {
                syncApp(apps.get(mt.name), appsDir, mt, payload);
                // if client has app, remove it from set
                apps.remove(mt.name);
            } else
                removeApp(apps.get(mt.name), appsDir, mt, payload);
        }

        // now do all the remaining apps the client doesn't have
        for (Map.Entry e : apps.entrySet())
            syncApp(e.getValue(), appsDir, new ModTime(e.getKey(), 0),
                                                        payload);
    }

    /**
     * Get the applications that should be
     * available to the specified server instance.
     */
    private Map getApps(Server server) {
        /*
        if (syncAllApps)
            return getAllApps();
        */

        Map apps = new HashMap();
        if (applications == null)
            return apps;        // no apps

        // all apps are under , even in a cluster
        for (ApplicationRef ref : server.getApplicationRef()) {
            Application app = applications.getApplication(ref.getRef());
            if (app != null) {
                if (logger.isLoggable(Level.FINEST))
                    logger.finest("ServerSynchronizer: got app " +
                                                                app.getName());
                if (Boolean.parseBoolean(app.getDirectoryDeployed())) {
                    if (logger.isLoggable(Level.FINEST))
                        logger.finest("ServerSynchronizer: " +
                                "skipping directory deployed app: " +
                                app.getName());
                } else
                    apps.put(VersioningUtils.getRepositoryName(app.getName()), app);
            }
        }
        return apps;
    }

    /*
    private Map getAllApps() {
        Map apps = new HashMap();
        if (applications == null)
            return apps;        // no apps

        for (ApplicationName module : applications.getModules()) {
            logger.finest("ServerSynchronizer: found module " +
                                                            module.getName());
            if (module instanceof Application) {
                final Application app = (Application)module;
                if (app.getObjectType().equals("user")) {
                    logger.finest("ServerSynchronizer: got app " +
                                                                app.getName());
                    if (Boolean.parseBoolean(app.getDirectoryDeployed()))
                        logger.fine("ServerSynchronizer: skipping directory " +
                                    "deployed app: " + app.getName());
                    else
                        apps.put(app.getName(), app);
                } else
                    logger.finest("ServerSynchronizer: found wrong app " +
                            app.getName() + ", type " + app.getObjectType());
            }
        }
        return apps;
    }
    */

    /**
     * Synchronize the application named by mt.name in the
     * base directory.  If the application is out of date,
     * add the application files to the payload, including
     * the generated files.
     */
    private boolean syncApp(Application app, File base, ModTime mt,
                            Payload.Outbound payload)
                            throws URISyntaxException {
        if (logger.isLoggable(Level.FINER))
            logger.finer("ServerSynchronizer: sync app " + mt.name);
        try {
            File appDir = fileOf(base, mt.name);
            if (syncArchive) {
                File archive = app.application();
                if (logger.isLoggable(Level.FINEST))
                    logger.finest("ServerSynchronizer: check archive " +
                                                                    archive);
                if (mt.time != 0 && archive.lastModified() == mt.time)
                    return false;     // success, nothing to do

                // attach the archive file
                attachAppArchive(archive, payload);
                /*
                 * Note that we don't need the deployment plan because
                 * we're not going to actually deploy it on the server
                 * instance, we're just going to unzip it.
                 */
            } else {
                if (logger.isLoggable(Level.FINEST))
                    logger.finest("ServerSynchronizer: check app dir " +
                                                                        appDir);
                if (mt.time != 0 && appDir.lastModified() == mt.time)
                    return false;     // success, nothing to do

                /*
                 * Recursively attach the application directory and
                 * all the generated directories.  The client will
                 * remove the old versions before installing the new ones.
                 */
                if (logger.isLoggable(Level.FINE))
                    logger.fine("ServerSynchronizer: sending files for " +
                                "application " + mt.name +
                                (mt.time == 0 ?
                                    " because it doesn't exist on the instance":
                                    " because it was out of date"));
                attachAppDir(appDir, payload);
            }

            // in either case, we attach the generated artifacts
            File gdir;
            gdir = env.getApplicationCompileJspPath();
            attachAppDir(fileOf(gdir, mt.name), payload);
            gdir = env.getApplicationGeneratedXMLPath();
            attachAppDir(fileOf(gdir, mt.name), payload);
            gdir = env.getApplicationEJBStubPath();
            attachAppDir(fileOf(gdir, mt.name), payload);
            gdir = new File(env.getApplicationStubPath(), "policy");
            attachAppDir(fileOf(gdir, mt.name), payload);
            // and also the altdd dir
            gdir = env.getApplicationAltDDPath();
            attachAppDir(fileOf(gdir, mt.name), payload);

        } catch (IOException ioex) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("ServerSynchronizer: IOException syncing app " +
                                                                    mt.name);
                logger.fine(ioex.toString());
            }
        }
        return true;
    }

    /**
     * Synchronize the lib directory.
     */
    private void synchronizeLib(Payload.Outbound payload,
                                    Server server, SyncRequest sr)
                                    throws URISyntaxException {
        List skip = new ArrayList();
        skip.add("databases");
        synchronizeDirectory(payload, server, sr,
                                env.getLibPath(), skip, SyncLevel.RECURSIVE);
    }

    /**
     * Synchronize the docroot directory.
     */
    private void synchronizeDocroot(Payload.Outbound payload,
                                    Server server, SyncRequest sr)
                                    throws URISyntaxException {
        synchronizeDirectory(payload, server, sr,
                                new File(env.getDomainRoot(), "docroot"), null,
                                SyncLevel.DIRECTORY);
    }

    /**
     * Synchronize a directory.
     */
    private void synchronizeDirectory(Payload.Outbound payload,
            Server server, SyncRequest sr, File dir, List skip,
            SyncLevel level) throws URISyntaxException {
        if (logger.isLoggable(Level.FINEST))
            logger.finest("ServerSynchronizer: directory is " + dir);
        List fileSet = getFileNames(dir, skip, level);
        synchronizeDirectory(payload, server, sr, dir, fileSet);
    }

    private void synchronizeDirectory(Payload.Outbound payload,
            Server server, SyncRequest sr, File dir, List fileSet)
            throws URISyntaxException {

        for (ModTime mt : sr.files) {
            if (fileSet.contains(mt.name)) {
                // if client has file, remove it from set
                fileSet.remove(mt.name);
                syncFile(domainRootUri, dir, mt, payload);
            } else
                removeFile(domainRootUri, dir, mt, payload);
        }

        // now do all the remaining files the client doesn't have
        for (String name : fileSet)
            syncFile(domainRootUri, dir, new ModTime(name, 0), payload);
    }

    /**
     * Synchronize the config-specific directory.
     * The directory for the instance is in the instance-config-specific
     * config directory, which is in the main config directory.
     * The instance-config-specific config directory is named
     * .
     */
    private void synchronizeConfigSpecificDir(Payload.Outbound payload,
            Server server, SyncRequest sr)
            throws URISyntaxException {
        String configDirName = server.getConfigRef();
        File configDir = env.getConfigDirPath();
        File configSpecificDir = new File(configDir, configDirName);
        if (logger.isLoggable(Level.FINEST))
            logger.finest("ServerSynchronizer: " +
                        "config-specific directory is " + configSpecificDir);
        if (!configSpecificDir.exists()) {
            if (logger.isLoggable(Level.FINE))
                logger.fine("ServerSynchronizer: no config-specific " +
                        "directory to synchronize: " + configSpecificDir);
            return;             // nothing to do
        }

        List fileSet = new ArrayList();
        getFileNames(configSpecificDir, configDir, null, fileSet,
                                                        SyncLevel.DIRECTORY);
        synchronizeDirectory(payload, server, sr, configDir, fileSet);
    }

    /**
     * Return a list with the names of all the
     * files in the specified directory.
     */
    private List getFileNames(File dir, List skip,
                                        SyncLevel level) {
        List names = new ArrayList();
        if (dir.exists())
            getFileNames(dir, dir, skip, names, level);
        else {
            if (logger.isLoggable(Level.FINEST))
                logger.finest("ServerSynchronizer: directory doesn't exist: " +
                                                                        dir);
        }
        return names;
    }

    /**
     * Get the mod times for the entries in dir and add them to the
     * SyncRequest, using names relative to baseDir.  If level is
     * RECURSIVE, check subdirectories and only include times for files,
     * and empty directories.
     */
    private int getFileNames(File dir, File baseDir, List skip,
                                List names, SyncLevel level) {
        if (level == SyncLevel.TOP) {
            String name = baseDir.toURI().relativize(dir.toURI()).getPath();
            // if name is a directory, it will end with "/"
            if (name.endsWith("/"))
                name = name.substring(0, name.length() - 1);
            names.add(name);
            return 1;     // nothing else
        }
        int cnt = 0;
        for (String file : dir.list()) {
            File f = new File(dir, file);
            String name = baseDir.toURI().relativize(f.toURI()).getPath();
            // if name is a directory, it will end with "/"
            if (name.endsWith("/"))
                name = name.substring(0, name.length() - 1);
            if (skip != null && skip.contains(name))
                continue;
            if (f.isDirectory() && level == SyncLevel.RECURSIVE) {
                int subFileCnt = getFileNames(f, baseDir, skip, names, level);
                if (subFileCnt == 0) {
                    names.add(name);
                    cnt++;
                } else {
                    cnt += subFileCnt;
                }
            } else {
                names.add(name);
                cnt++;
            }
        }
        return cnt;
    }

    /**
     * Attach the application archive file to the payload.
     */
    private void attachAppArchive(File file, Payload.Outbound payload)
                                throws IOException {
        if (logger.isLoggable(Level.FINER)) {
            logger.finer("ServerSynchronizer: domainRootUri " + domainRootUri);
            logger.finer("ServerSynchronizer: file.toURI() " + file.toURI());
            logger.finer("ServerSynchronizer: attach file " +
                            domainRootUri.relativize(file.toURI()));
        }
        payload.attachFile("application/octet-stream",
            domainRootUri.relativize(file.toURI()),
            "configChange", file, true);
    }

    /**
     * Attach the application directory and all its contents to the payload.
     */
    private void attachAppDir(File dir, Payload.Outbound payload)
                                throws IOException {
        if (logger.isLoggable(Level.FINER))
            logger.finer("ServerSynchronizer: attach directory " +
                            domainRootUri.relativize(dir.toURI()));
        if (!dir.exists()) {
            logger.finer("ServerSynchronizer: nothing to attach");
            return;
        }
        payload.requestFileReplacement("application/octet-stream",
            domainRootUri.relativize(dir.toURI()),
            "configChange", null, dir, true);
    }

    /**
     * Send requests to the client to remove the specified app directory
     * and all the generated directories.
     */
    private void removeApp(Application app, File base, ModTime mt,
                            Payload.Outbound payload)
                            throws URISyntaxException {
        if (logger.isLoggable(Level.FINE))
            logger.fine("ServerSynchronizer: removing files for application " +
                        mt.name +
                        " because it is no longer deployed to this instance");
        try {
            File dir = fileOf(base, mt.name);
            removeDir(dir, payload);
            dir = env.getApplicationCompileJspPath();
            removeDir(fileOf(dir, mt.name), payload);
            dir = env.getApplicationGeneratedXMLPath();
            removeDir(fileOf(dir, mt.name), payload);
            dir = env.getApplicationEJBStubPath();
            removeDir(fileOf(dir, mt.name), payload);
            dir = new File(env.getApplicationStubPath(), "policy");
            removeDir(fileOf(dir, mt.name), payload);
        } catch (IOException ioex) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("ServerSynchronizer: IOException removing app " +
                                                                    mt.name);
                logger.fine(ioex.toString());
            }
        }
    }

    /**
     * Request recursive removal of the specified directory.
     */
    private void removeDir(File file, Payload.Outbound payload)
                                throws IOException {
        payload.requestFileRemoval(
            domainRootUri.relativize(file.toURI()),
            "configChange", null, true);    // recursive removal
    }

    /**
     * Return a File representing the URI relative to the base directory.
     */
    private File fileOf(File base, String uri) throws URISyntaxException {
        // have to use string concatenation to combine the relative URI
        // with the base URI because URI.resolve() ignores the last
        // component of the base URI
        return new File(new URI(base.toURI().toString() + "/" + uri));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy