/*
* Copyright (c) 2010-2012 Thiago T. Sá
*
* This file is part of CloudReports.
*
* CloudReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CloudReports is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* For more information about your rights as a user of CloudReports,
* refer to the LICENSE file or see <http://www.gnu.org/licenses/>.
*/
package cloudreports.extensions;
import cloudreports.utils.FileIO;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Provides static methods that load user-implemented extensions using
* the Java Reflection API.
* It is entirely based on the content of the extensions/classnames.xml file.
*
* @author Thiago T. Sá
* @since 1.0
*/
public class ExtensionsLoader {
/** The parameters array passed to the URLClassLoader. */
private static final Class<?>[] parameters = new Class[]{URL.class};
/** The list of the loaded extension files. */
private static List<String> addedFiles = new ArrayList<String>();
/** The path to the extensions folder. */
private static String extensionsPath = FileIO.getPathOfExecutable() + "extensions";
/** The path to the classnames.xml file. */
private static String classnamesXmlPath = FileIO.getPathOfExecutable() + "extensions/classnames.xml";
/** The classnames document. */
private static Document classnamesXml = getClassnamesXml();
/**
* Loads an extension file.
*
* @param canonicalPath the canonical path to the extension file.
* @throws IOException if the system classloader could not load the
* extension.
* @since 1.0
*/
private static void addFile(String canonicalPath) throws IOException {
if(addedFiles.contains(canonicalPath)) return;
File f = new File(canonicalPath);
addURL(f.toURI().toURL());
addedFiles.add(canonicalPath);
}
/**
* Loads an extension file using its URL.
*
* @param url the URL of the extension file.
* @throws IOException if the system classloader could not load the
* extension.
* @since 1.0
*/
private static void addURL(URL url) throws IOException {
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class<?> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ url });
} catch (Throwable t) {
throw new IOException("Error: could not add URL to system classloader");
}
}
/**
* Gets the aliases of all extension implementations of a given base class.
* It reads the classnames.xml file to get the list of aliases.
*
* @param type the type of the extension.
* @return a list of aliases of all extension implementations
* of the given base class.
* @since 1.0
*/
public static List<String> getExtensionsAliasesByType(String type) {
List<String> listAliases = new ArrayList<String>();
if(classnamesXml == null) return listAliases;
NodeList classNodes = classnamesXml.getElementsByTagName("class");
for(int index = 0; index < classNodes.getLength(); index++) {
try {
if(classNodes.item(index).getAttributes().getNamedItem("type").getNodeValue().equals(type))
listAliases.add(classNodes.item(index).getAttributes().getNamedItem("alias").getNodeValue());
}
catch (NullPointerException e) {
continue;
}
}
return listAliases;
}
/**
* Gets a specific user-implemented extension object based on its base class
* and its alias.
*
* @param type the type of the extension.
* @param alias the alias of the extension.
* @param constructorTypes the types used by the constructor of the
* extension class.
* @param constructorArguments the arguments to be used in the
* constructor of the extension class.
* @return an instance of the extension class.
* @since 1.0
*/
public static Object getExtension(String type, String alias, Class<?>[] constructorTypes, Object[] constructorArguments) {
String filename = getFileName(type, alias);
String classname = getClassnameOfExtension(type, alias);
if(filename == null || classname == null) return null;
File extensionFile = new File(extensionsPath + "/" + filename);
if(!extensionFile.exists() || !extensionFile.isFile()) return null;
try {
addFile(extensionFile.getCanonicalPath());
if(constructorTypes != null && constructorArguments != null)
return ClassLoader.getSystemClassLoader().loadClass(classname).getConstructor(constructorTypes).newInstance(constructorArguments);
else return ClassLoader.getSystemClassLoader().loadClass(classname).getConstructor().newInstance();
} catch (Exception ex) {
Logger.getLogger(ExtensionsLoader.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
/**
* Gets the name of the file that contains the extension class based on
* the base classname and the extension alias.
* It reads the classnames.xml file to get name of the file.
*
* @param type the type of the extension.
* @param alias the alias of the extension.
* @return the name of the file that contains the
* extension class.
* @since 1.0
*/
private static String getFileName(String type, String alias) {
if(classnamesXml == null) return null;
NodeList classNodes = classnamesXml.getElementsByTagName("class");
for(int index = 0; index < classNodes.getLength(); index++) {
if(classNodes.item(index).getAttributes().getNamedItem("type").getNodeValue().equals(type) &&
classNodes.item(index).getAttributes().getNamedItem("alias").getNodeValue().equals(alias))
return classNodes.item(index).getAttributes().getNamedItem("filename").getNodeValue();
}
return null;
}
/**
* Gets the classname of a extension class based on its base classname
* and its alias.
* It reads the classnames.xml file to get classname.
*
* @param type the type of the extension.
* @param alias the alias of the extension.
* @return the classname of the extension class.
* @since 1.0
*/
private static String getClassnameOfExtension(String type, String alias) {
if(classnamesXml == null) return null;
NodeList classNodes = classnamesXml.getElementsByTagName("class");
for(int index = 0; index < classNodes.getLength(); index++) {
if(classNodes.item(index).getAttributes().getNamedItem("type").getNodeValue().equals(type) &&
classNodes.item(index).getAttributes().getNamedItem("alias").getNodeValue().equals(alias))
return classNodes.item(index).getAttributes().getNamedItem("name").getNodeValue();
}
return null;
}
/**
* Loads an instance of <code>Document</code> that contains the
* classnames.xml file.
*
* @return a <code>Document</code> object
* containing the classnames.xml file.
* @since 1.0
*/
private static Document getClassnamesXml() {
File classnamesXmlFile = new File(classnamesXmlPath);
if(!classnamesXmlFile.exists() || !classnamesXmlFile.isFile()) {
return null;
}
DocumentBuilder db;
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
InputSource is = new InputSource(classnamesXmlFile.toURI().toURL().openStream());
Document classnamesXmlDocument = db.parse(is);
return classnamesXmlDocument;
} catch (Exception ex) {
Logger.getLogger(ExtensionsLoader.class.getName()).log(Level.INFO, null, ex);
return null;
}
}
}