com.sun.enterprise.v3.admin.cluster.ServerSynchronizer Maven / Gradle / Ivy
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2011 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 org.glassfish.api.admin.*;
import org.jvnet.hk2.annotations.*;
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;
/**
* 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 te domain directory.
*
* @author Bill Shannon
*/
@Service
@Scoped(PerLookup.class)
public final class ServerSynchronizer implements PostConstruct {
@Inject
private ServerEnvironment env;
@Inject
private Domain domain;
@Inject(optional = true)
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 {
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) {
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) {
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 (mt.time == 0)
logger.fine("ServerSynchronizer: sending file " + f +
" because it doesn't exist on the instance");
else
logger.fine("ServerSynchronizer: sending file " + f +
" because it was out of date");
payload.requestFileReplacement("application/octet-stream",
root.relativize(f.toURI()),
"configChange", null, f, true);
} catch (IOException ioex) {
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 {
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) {
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 {
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) {
logger.finest("ServerSynchronizer: got app " + app.getName());
if (Boolean.parseBoolean(app.getDirectoryDeployed()))
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 {
logger.finer("ServerSynchronizer: sync app " + mt.name);
try {
File appDir = fileOf(base, mt.name);
if (syncArchive) {
File archive = app.application();
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 {
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 (mt.time == 0)
logger.fine("ServerSynchronizer: sending files for " +
"application " + mt.name +
" because it doesn't exist on the instance");
else
logger.fine("ServerSynchronizer: sending files for " +
"application " + mt.name +
" 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);
} catch (IOException ioex) {
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 {
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);
logger.finest("ServerSynchronizer: " +
"config-specific directory is " + configSpecificDir);
if (!configSpecificDir.exists()) {
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
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,
* not directories.
*/
private void 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; // nothing else
}
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) {
getFileNames(f, baseDir, skip, names, level);
} else {
names.add(name);
}
}
}
/**
* 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 {
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) {
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));
}
}