/**
* Open Wonderland
*
* Copyright (c) 2010, Open Wonderland Foundation, All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/
/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.modules.service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.jdesktop.wonderland.modules.Module;
import org.jdesktop.wonderland.modules.ModuleFactory;
/**
* The PendingManager class manages all modules that are pending and waiting
* to be installed.
*
* @author Jordan Slott <jslott@dev.java.net>
*/
public class PendingManager {
/* The directory in which pending modules are kept */
private static final String PENDING_DIR = "pending";
private File pendingFile = null;
/* The Map of modules pending to be installed and their Module objects */
private Map<String, Module> pendingModules = null;
/* The chunk size to write out while expanding archive files to disk */
private static final int CHUNK_SIZE = (8 * 1024);
/**
* Constructor, takes the root directory of the module manager
*/
public PendingManager(File root) {
/* Create the pending directory if it does not exist */
this.pendingFile = new File(root, PENDING_DIR);
try {
ModuleManagerUtils.makeDirectory(this.pendingFile);
} catch (java.io.IOException excp) {
ModuleManager.getLogger().log(Level.SEVERE,
"[MODULES] Failed to Create Pending Directory ",
excp);
}
/* Read the map of pending modules */
this.pendingModules = Collections.synchronizedMap(this.fetchModules());
}
/**
* Returns the hashmap of pending modules
*/
public Map<String, Module> getModules() {
return this.pendingModules;
}
/**
* Adds a new module to be pending. Returns the new module object, or null
* upon error.
*/
public Module add(File jarFile) {
/* Get the error logger */
Logger logger = ModuleManager.getLogger();
/* First attempt to open the URL as a module */
Module module = null;
try {
module = ModuleFactory.open(jarFile);
} catch (java.lang.Exception excp) {
/* Log the error and return false */
logger.log(Level.WARNING, "[MODULES] PENDING Failed to Open Module "
+ jarFile, excp);
return null;
}
/* Next, see the module already exists, log warning and continue */
if (this.pendingModules.containsKey(module.getName()) == true) {
logger.log(Level.INFO, "[MODULES] PENDING Module already exists "
+ module.getName());
}
/* Add to the pending/ directory */
File file = this.addToPending(module.getName(), jarFile);
if (file == null) {
logger.log(Level.WARNING, "[MODULES] PENDING Failed to add " +
module.getName());
return null;
}
/* Re-open the module in the new directory */
try {
module = ModuleFactory.open(file);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Add pending module " + module);
}
} catch (java.lang.Exception excp) {
/* Log the error and return false */
logger.log(Level.WARNING, "[MODULES] PENDING Failed to Open Module "
+ file, excp);
return null;
}
/* If successful, add to the list of pending modules */
this.pendingModules.put(module.getName(), module);
return module;
}
/**
* Removes an existing module, given its name.
*/
public void remove(String moduleName) {
Logger logger = ModuleManager.getLogger();
/*
* Simply delete the directory associated with the module (quietly) and
* remove from the list
*/
File file = new File(this.pendingFile, moduleName);
try {
FileUtils.deleteDirectory(file);
} catch (IOException excp) {
/* Log an error and continue */
logger.log(Level.WARNING, "[MODULES] PENDING Failed to remove " +
file.getAbsolutePath(), excp);
}
this.pendingModules.remove(moduleName);
}
/**
* Adds a new module to pending. This method expands the module (as
* represented by a jar) to the pending directory. This simply copies files,
* it assumes all preparations or checks have already been performed.
*/
private File addToPending(String moduleName, File jarFile) {
/* The error logger */
Logger logger = ModuleManager.getLogger();
/*
* Expand the contents of the module to the pending/ directory. First
* create a directory holding the module (but check first if it already
* exists and log a warning message).
*/
File file = new File(this.pendingFile, moduleName);
if (ModuleManagerUtils.makeCleanDirectory(file) == false) {
logger.log(Level.WARNING, "[MODULES] PENDING Failed to Create " +
file.getAbsolutePath());
return null;
}
/* Next, expand the contents of the module into this directory */
try {
this.expand(file, jarFile);
} catch (java.io.IOException excp) {
logger.log(Level.WARNING, "[MODULES] PENDING Failed to Expand " +
jarFile, excp);
return null;
}
return file;
}
/**
* Returns a map of module names and objects from a given directory. If no
* modules are present, this method returns an empty map.
*
* @return An map of unique module names and their Module objects
*/
private Map<String, Module> fetchModules() {
Logger logger = ModuleManager.getLogger();
Map<String, Module> map = new HashMap<String, Module>();
/*
* Loop through each file and check that it is potentially valid.
* If so, add its name to the map of module names
*/
File[] files = this.pendingFile.listFiles();
for (File file : files) {
/* Attempt to create the module */
try {
Module module = ModuleFactory.open(file);
map.put(module.getName(), module);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Load pending module " + module);
}
} catch (java.lang.Exception excp) {
ModuleManager.getLogger().log(Level.WARNING,
"[MODULES] Invalid module " + file, excp);
}
}
return map;
}
/**
* Takes a base directory (which must exist and be readable) and expands
* the contents of the archive module into that directory given the
* URL of the module encoded as a jar file
*
* @param root The base directory in which the module is expanded
* @throw IOException Upon error
*/
private void expand(File root, File jar) throws IOException {
/*
* Loop through each entry, fetch its input stream, and write to an
* output stream for the file.
*/
JarFile jarFile = new JarFile(jar);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements() == true) {
/* Fetch the next entry, its name is the relative file name */
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
long size = entry.getSize();
/* Don't expand anything that beings with META-INF */
if (entryName.startsWith("META-INF") == true) {
continue;
}
/* Ignore if it is a directory, then create it */
if (entryName.endsWith("/") == true) {
File file = new File(root, entryName);
file.mkdirs();
continue;
}
/* Write out to a file in 'root' */
File file = new File(root, entryName);
InputStream jis = jarFile.getInputStream(entry);
FileOutputStream os = new FileOutputStream(file);
byte[] b = new byte[PendingManager.CHUNK_SIZE];
long read = 0;
while (read < size) {
int len = jis.read(b);
if (len == -1) {
break;
}
read += len;
os.write(b, 0, len);
}
jis.close();
os.close();
}
}
}