openfire源码分析—2
XMPPServer构造函数
public XMPPServer() {
// We may only have one instance of the server running on the JVM
if (instance != null) {
throw new IllegalStateException("A server is already running");
}
instance = this;
start();
}
这里主要就是调用了start函数。
start()
public void start() {
try {
initialize();
// Create PluginManager now (but don't start it) so that modules may use it
File pluginDir = new File(openfireHome, "plugins");
pluginManager = new PluginManager(pluginDir);
// If the server has already been setup then we can start all the server's modules
//***************************************
// setupMode = false;
//*****************************************
if (!setupMode) {
verifyDataSource();
// First load all the modules so that modules may access other modules while
// being initialized
loadModules();
// Initize all the modules
initModules();
// Start all the modules
startModules();
}
// Initialize statistics
ServerTrafficCounter.initStatistics();
// Load plugins (when in setup mode only the admin console will be loaded)
pluginManager.start();
// Log that the server has been started
String startupBanner = LocaleUtils.getLocalizedString("short.title") + " " + version.getVersionString() +
" [" + JiveGlobals.formatDateTime(new Date()) + "]";
logger.info(startupBanner);
System.out.println(startupBanner);
started = true;
// Notify server listeners that the server has been started
for (XMPPServerListener listener : listeners) {
listener.serverStarted();
}
}
catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.out.println(LocaleUtils.getLocalizedString("startup.error"));
shutdownServer();
}
}
initialize()主要做了一些初始化工作。
pluginManager = new PluginManager(pluginDir)根据插件目录pluginDir初始化PluginManager实例,该构造函数如下所示
public PluginManager(File pluginDir) {
this.pluginDirectory = pluginDir;
plugins = new ConcurrentHashMap<String, Plugin>();
pluginDirs = new HashMap<Plugin, File>();
pluginFiles = new HashMap<String, File>();
classloaders = new HashMap<Plugin, PluginClassLoader>();
pluginDevelopment = new HashMap<Plugin, PluginDevEnvironment>();
parentPluginMap = new HashMap<Plugin, List<String>>();
childPluginMap = new HashMap<Plugin, String>();
devPlugins = new HashSet<String>();
pluginMonitor = new PluginMonitor();
}
重点关注一下PluginMonitor函数,该函数后面还会提到。
接着,verifyDataSource()、loadModules()、initModules()和startModules()四个函数用来装载一些模块,这几个函数留在下一章分析。
ServerTrafficCounter.initStatistics()用来初始化一些统计信息,例如openfire进程输入输出的字节数等等。
pluginManager.start()启动插件管理,后面再来看这个函数。
最后调用了一些监听函数。
initialize()
private void initialize() throws FileNotFoundException {
locateOpenfire();
startDate = new Date();
try {
host = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException ex) {
logger.warn("Unable to determine local hostname.", ex);
}
if (host == null) {
host = "127.0.0.1";
}
version = new Version(3, 10, 2, Version.ReleaseStatus.Release, -1);
if ("true".equals(JiveGlobals.getXMLProperty("setup"))) {
setupMode = false;
}
if (isStandAlone()) {
logger.info("Registering shutdown hook (standalone mode)");
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread());
TaskEngine.getInstance().schedule(new Terminator(), 1000, 1000);
}
loader = Thread.currentThread().getContextClassLoader();
try {
CacheFactory.initialize();
} catch (InitializationException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
JiveGlobals.migrateProperty("xmpp.domain");
name = JiveGlobals.getProperty("xmpp.domain", host).toLowerCase();
JiveGlobals.migrateProperty(Log.LOG_DEBUG_ENABLED);
Log.setDebugEnabled(JiveGlobals.getBooleanProperty(Log.LOG_DEBUG_ENABLED, false));
// Update server info
xmppServerInfo = new XMPPServerInfoImpl(name, host, version, startDate);
initialized = true;
}
locateOpenfire()用于确定openfire的工作目录,以及配置文件,并构造相应的File实例,我们后面看这个函数。
InetAddress.getLocalHost().getHostName()用于获取计算机名。
Version用于存放本次openfire的版本信息,因此本次openfire的版本为3.10.2-release。
isStandAlone用于判断服务器模式,standalone的服务器表是没有被第三方软件管理(例如服务的启动、停止、安全等)。
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread())用于设置服务器异常关机时执行的函数。ShutdownHookThread如下所示
private class ShutdownHookThread extends Thread {
/**
* <p>Logs the server shutdown.</p>
*/
@Override
public void run() {
shutdownServer();
logger.info("Server halted");
System.err.println("Server halted");
}
}
因此当服务器异常关机时,必然会执行shutdownServer函数,关闭一些模块、执行一些监听函数等等。这里就不往下看了。
TaskEngine.getInstance().schedule(new Terminator(), 1000, 1000)用来启动一个定时线程,如下所示
private class Terminator extends TimerTask {
private BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
public void run() {
try {
if (stdin.ready()) {
if (EXIT.equalsIgnoreCase(stdin.readLine())) {
System.exit(0); // invokes shutdown hook(s)
}
}
} catch (IOException ioe) {
logger.error("Error reading console input", ioe);
}
}
}
该线程监听控制台的输入,如果为“exit”,则调用System.exit退出openfire进程。
继续往下看,CacheFactory.initialize()的代码如下
public static synchronized void initialize() throws InitializationException {
try {
localCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(localCacheFactoryClass).newInstance();
cacheFactoryStrategy = localCacheFactoryStrategy;
} catch (Exception e) {
log.error("Failed to instantiate local cache factory strategy: " + localCacheFactoryClass, e);
throw new InitializationException(e);
}
}
该函数初始化了org.jivesoftware.util.cache.DefaultLocalCacheStrategy实例,该实例和缓存有关,因为该构造函数为空函数,所以我们不往下分析了。
再往下看,migrateProperty用于将数据从xml搬入数据库,该函数如下所示
public static void migrateProperty(String name) {
if (isSetupMode()) {
return;
}
if (openfireProperties == null) {
loadOpenfireProperties();
}
openfireProperties.migrateProperty(name);
}
isSetupMode用于启动数据库,该函数如下所示
private static boolean isSetupMode() {
if (Boolean.valueOf(JiveGlobals.getXMLProperty("setup"))) {
return false;
}
// Check if the DB configuration is done
if (DbConnectionManager.getConnectionProvider() == null) {
// DB setup is still not completed so setup is needed
return true;
}
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
// Properties can now be loaded from DB so consider setup done
}
catch (SQLException e) {
// Properties cannot be loaded from DB so do not consider setup done
return true;
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return false;
}
继续看migrateProperty,loadOpenfireProperties用于将openfire的配置文件转化为XMLProperties实例,该函数如下所示
private synchronized static void loadOpenfireProperties() {
if (openfireProperties == null) {
// If home is null then log that the application will not work correctly
if (home == null && !failedLoading) {
failedLoading = true;
StringBuilder msg = new StringBuilder();
msg.append("Critical Error! The home directory has not been configured, \n");
msg.append("which will prevent the application from working correctly.\n\n");
System.err.println(msg.toString());
}
// Create a manager with the full path to the Openfire config file.
else {
try {
openfireProperties = new XMLProperties(home + File.separator + getConfigName());
}
catch (IOException ioe) {
Log.error(ioe.getMessage());
failedLoading = true;
}
}
// create a default/empty XML properties set (helpful for unit testing)
if (openfireProperties == null) {
try {
openfireProperties = new XMLProperties();
} catch (IOException e) {
Log.error("Failed to setup default openfire properties", e);
}
}
}
}
可以看到,如果找不到配置文件,这里就实例化一个空的XMLProperties实例。继续看migrateProperty中最后的openfireProperties.migrateProperty(name),该函数用来检查openfire.xml文件中的配置是否与数据库中一致,如果不一致以数据库中的配置为准,并且删除openfire.xml的配置。由于该函数涉及到太多的XML操作和数据库操作,因此这里就不看了。
initialize函数最后初始化了XMPPServerInfoImpl实例,如下所示
public XMPPServerInfoImpl(String xmppDomain, String hostname, Version version, Date startDate) {
this.xmppDomain = xmppDomain;
this.hostname = hostname;
this.ver = version;
this.startDate = startDate;
}
locateOpenfire()
private void locateOpenfire() throws FileNotFoundException {
String jiveConfigName = "conf" + File.separator + "openfire.xml";
// First, try to load it openfireHome as a system property.
if (openfireHome == null) {
String homeProperty = System.getProperty("openfireHome");
try {
if (homeProperty != null) {
openfireHome = verifyHome(homeProperty, jiveConfigName);
}
}
catch (FileNotFoundException fe) {
// Ignore.
}
}
// If we still don't have home, let's assume this is standalone
// and just look for home in a standard sub-dir location and verify
// by looking for the config file
if (openfireHome == null) {
try {
openfireHome = verifyHome("..", jiveConfigName).getCanonicalFile();
}
catch (FileNotFoundException fe) {
// Ignore.
}
catch (IOException ie) {
// Ignore.
}
}
// If home is still null, no outside process has set it and
// we have to attempt to load the value from openfire_init.xml,
// which must be in the classpath.
if (openfireHome == null) {
InputStream in = null;
try {
in = getClass().getResourceAsStream("/openfire_init.xml");
if (in != null) {
SAXReader reader = new SAXReader();
Document doc = reader.read(in);
String path = doc.getRootElement().getText();
try {
if (path != null) {
openfireHome = verifyHome(path, jiveConfigName);
}
}
catch (FileNotFoundException fe) {
fe.printStackTrace();
}
}
}
catch (Exception e) {
System.err.println("Error loading openfire_init.xml to find home.");
e.printStackTrace();
}
finally {
try {
if (in != null) {
in.close();
}
}
catch (Exception e) {
System.err.println("Could not close open connection");
e.printStackTrace();
}
}
}
if (openfireHome == null) {
System.err.println("Could not locate home");
throw new FileNotFoundException();
}
else {
// Set the home directory for the config file
JiveGlobals.setHomeDirectory(openfireHome.toString());
// Set the name of the config file
JiveGlobals.setConfigName(jiveConfigName);
}
}
jiveConfigName为openfire配置文件所在的相对路径(conf/openfire.xml)。
String homeProperty = System.getProperty(“openfireHome”)用来获取openfire工作目录的绝对路径。
openfireHome = verifyHome(homeProperty, jiveConfigName)用来获取配置文件的File实例。该函数如下所示
private File verifyHome(String homeGuess, String jiveConfigName) throws FileNotFoundException {
File openfireHome = new File(homeGuess);
File configFile = new File(openfireHome, jiveConfigName);
if (!configFile.exists()) {
throw new FileNotFoundException();
}
else {
try {
return new File(openfireHome.getCanonicalPath());
}
catch (Exception ex) {
throw new FileNotFoundException();
}
}
}
继续locateOpenfire的分析,第二个if表是,如果获取配置文件的File实例失败,就尝试从上级目录(..)重新获取一遍。
如果继续失败,就尝试从openfire_init.xml配置文件中获取openfire工作目录的绝对路径,继续尝试一遍。
如果再找不到,就抛出异常。
如果成功获得了openfire.xml配置文件的File实例,就调用JiveGlobals设置一些全局变量。这里设置了openfire的工作目录的绝对路径以及openfire.xml配置文件的相对路径。
pluginManager.start()
public void start() {
executor = new ScheduledThreadPoolExecutor(1);
// See if we're in development mode. If so, check for new plugins once every 5 seconds.
// Otherwise, default to every 20 seconds.
if (Boolean.getBoolean("developmentMode")) {
executor.scheduleWithFixedDelay(pluginMonitor, 0, 5, TimeUnit.SECONDS);
}
else {
executor.scheduleWithFixedDelay(pluginMonitor, 0, 20, TimeUnit.SECONDS);
}
}
因此该函数主要启动了pluginMonitor线程,执行了其中的run函数,如下所示
public void run() {
// If the task is already running, return.
synchronized (this) {
if (running) {
return;
}
running = true;
}
try {
running = true;
// Look for extra plugin directories specified as a system property.
String pluginDirs = System.getProperty("pluginDirs");
if (pluginDirs != null) {
StringTokenizer st = new StringTokenizer(pluginDirs, ", ");
while (st.hasMoreTokens()) {
String dir = st.nextToken();
if (!devPlugins.contains(dir)) {
loadPlugin(new File(dir));
devPlugins.add(dir);
}
}
}
File[] jars = pluginDirectory.listFiles(new FileFilter() {
public boolean accept(File pathname) {
String fileName = pathname.getName().toLowerCase();
return (fileName.endsWith(".jar") || fileName.endsWith(".war"));
}
});
if (jars == null) {
return;
}
for (File jarFile : jars) {
String pluginName = jarFile.getName().substring(0,
jarFile.getName().length() - 4).toLowerCase();
// See if the JAR has already been exploded.
File dir = new File(pluginDirectory, pluginName);
// Store the JAR/WAR file that created the plugin folder
pluginFiles.put(pluginName, jarFile);
// If the JAR hasn't been exploded, do so.
if (!dir.exists()) {
unzipPlugin(pluginName, jarFile, dir);
}
// See if the JAR is newer than the directory. If so, the plugin
// needs to be unloaded and then reloaded.
else if (jarFile.lastModified() > dir.lastModified()) {
// If this is the first time that the monitor process is running, then
// plugins won't be loaded yet. Therefore, just delete the directory.
if (firstRun) {
int count = 0;
// Attempt to delete the folder for up to 5 seconds.
while (!deleteDir(dir) && count < 5) {
Thread.sleep(1000);
}
}
else {
unloadPlugin(pluginName);
}
// If the delete operation was a success, unzip the plugin.
if (!dir.exists()) {
unzipPlugin(pluginName, jarFile, dir);
}
}
}
File[] dirs = pluginDirectory.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
// Sort the list of directories so that the "admin" plugin is always
// first in the list.
Arrays.sort(dirs, new Comparator<File>() {
public int compare(File file1, File file2) {
if (file1.getName().equals("admin")) {
return -1;
}
else if (file2.getName().equals("admin")) {
return 1;
}
else {
return file1.compareTo(file2);
}
}
});
// Turn the list of JAR/WAR files into a set so that we can do lookups.
Set<String> jarSet = new HashSet<String>();
for (File file : jars) {
jarSet.add(file.getName().toLowerCase());
}
// See if any currently running plugins need to be unloaded
// due to the JAR file being deleted (ignore admin plugin).
// Build a list of plugins to delete first so that the plugins
// keyset isn't modified as we're iterating through it.
List<String> toDelete = new ArrayList<String>();
for (File pluginDir : dirs) {
String pluginName = pluginDir.getName();
if (pluginName.equals("admin")) {
continue;
}
if (!jarSet.contains(pluginName + ".jar")) {
if (!jarSet.contains(pluginName + ".war")) {
toDelete.add(pluginName);
}
}
}
for (String pluginName : toDelete) {
unloadPlugin(pluginName);
}
// Load all plugins that need to be loaded.
for (File dirFile : dirs) {
// If the plugin hasn't already been started, start it.
if (dirFile.exists() && !plugins.containsKey(dirFile.getName())) {
loadPlugin(dirFile);
}
}
// Set that at least one iteration was done. That means that "all available" plugins
// have been loaded by now.
if (!XMPPServer.getInstance().isSetupMode()) {
executed = true;
}
// Trigger event that plugins have been monitored
firePluginsMonitored();
}
catch (Throwable e) {
Log.error(e.getMessage(), e);
}
// Finished running task.
synchronized (this) {
running = false;
}
// Process finished, so set firstRun to false (setting it multiple times doesn't hurt).
firstRun = false;
}
这里就不详细分析这个函数了,该函数就是解压插件目录下所有拓展名为jar和war的插件,解压后变成一个目录,然后调用loadPlugin装载该插件,最后通过firePluginsMonitored函数调用插件的监听函数。