/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 Lesser General Public License for more details.
*
* Copyright 2009 Pentaho Corporation. All rights reserved.
*
*/
package org.pentaho.platform.plugin.services.pluginmgr;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.pentaho.platform.api.engine.IPluginResourceLoader;
import org.pentaho.platform.api.engine.ISystemSettings;
import org.pentaho.platform.api.repository.ISolutionRepository;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.plugin.services.messages.Messages;
import org.pentaho.platform.util.logging.Logger;
import org.pentaho.platform.util.messages.LocaleHelper;
/**
* The default implementation of the {@link IPluginResourceLoader}. Obtains resources
* by searching the root directory of a {@link PluginClassLoader}.
*
* <h3>Resource discovery</h3>
* {@link PluginResourceLoader} will search the following places for plugin classes:
* <ul>
* <li> the /lib folder under the plugin's root directory, e.g. "myplugin/lib"
* </ul>
* {@link PluginResourceLoader} will search for non-class resources in several locations:
* <ul>
* <li> inside jar files located in the lib directory
* <li> from the filesystem relative to the root directory of the plugin
* </ul>
*
* <h3>resourcePath</h3> This class requires
* resource paths to be the relative paths to plugin resources, relative the root directory
* of the plugin. A resource path can be specified either using '/' or '.' (or both) in the path, depending
* on the particular method you are using. It is usually best to specify the path using '/' since
* both the filesystem and the classloader can handle this delimiter, whereas '.' will not be handled
* correctly if you are trying to load a resource from the filesystem.
*
* <h3>Plugin Settings</h3>This class backs the plugin settings APIs with the PentahoSystem settings service.
* See {@link PentahoSystem#getSystemSetting(String, String)} and {@link ISystemSettings}. System
* settings are expected in a file named settings.xml in the root of the plugin directory.
*
* @author aphillips
*
*/
public class PluginResourceLoader implements IPluginResourceLoader {
private File rootDir = null;
private PluginClassLoader overrideClassloader;
private String settingsPath = ISolutionRepository.SEPARATOR + "settings.xml"; //$NON-NLS-1$
public void setSettingsPath(String settingsPath) {
this.settingsPath = settingsPath;
}
@Deprecated
public void setOverrideClassloader(PluginClassLoader pluginClassloader) {
this.overrideClassloader = pluginClassloader;
}
protected PluginClassLoader getOverrideClassloader() {
return overrideClassloader;
}
/**
* Force the resource loader to look for resources in this root directory.
* If null, the resource loader will consult the {@link PluginClassLoader}
* for the root directory.
* @param rootDir the root directory in which to search for resources
* @deprecated instead of setting the root dir, have your application use a subclass of PluginResourceLoader
* that returns an appropriately pathed PluginClassLoader from an overridden {@link #getClassLoader(Class)}.
*/
public void setRootDir(File rootDir) {
this.rootDir = rootDir;
}
public byte[] getResourceAsBytes(Class<? extends Object> clazz, String resourcePath) {
InputStream in = getResourceAsStream(clazz, resourcePath);
if (in == null) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write(in);
} catch (IOException e) {
Logger.debug(this, "Cannot open stream to resource", e); //$NON-NLS-1$
return null;
} finally {
IOUtils.closeQuietly(in);
}
return out.toByteArray();
}
public String getResourceAsString(Class<? extends Object> clazz, String resourcePath)
throws UnsupportedEncodingException {
return getResourceAsString(clazz, resourcePath, LocaleHelper.getSystemEncoding());
}
public String getResourceAsString(Class<? extends Object> clazz, String resourcePath, String charsetName)
throws UnsupportedEncodingException {
byte[] bytes = getResourceAsBytes(clazz, resourcePath);
if (bytes == null) {
return null;
}
return new String(bytes, charsetName);
}
public String getSystemRelativePluginPath(ClassLoader classLoader) {
File dir = getPluginDir(classLoader);
if (dir == null) {
return null;
}
// get the full path with \ converted to /
String path = dir.getAbsolutePath().replace('\\', ISolutionRepository.SEPARATOR);
int pos = path.lastIndexOf(ISolutionRepository.SEPARATOR + "system" + ISolutionRepository.SEPARATOR); //$NON-NLS-1$
if (pos != -1) {
path = path.substring(pos + 8);
}
return path;
}
protected File getPluginDir(ClassLoader classLoader) {
if (rootDir != null) {
return rootDir;
}
if (classLoader instanceof PluginClassLoader) {
return ((PluginClassLoader) classLoader).getPluginDir();
}
return null;
}
/*
* It is important for this method to exist since it provides a way to override the classloader
* which is particularly useful in test cases
*/
protected ClassLoader getClassLoader(Class<?> clazz) {
PluginClassLoader _overrideClassloader = getOverrideClassloader();
ClassLoader classLoader = (_overrideClassloader != null) ? _overrideClassloader : clazz.getClassLoader();
if (!PluginClassLoader.class.isAssignableFrom(classLoader.getClass())) {
Logger.warn(this, Messages.getInstance().getString(
"PluginResourceLoader.WARN_CLASS_LOADED_OUTSIDE_OF_PLUGIN_ENV", clazz.getName(), PluginClassLoader.class.getSimpleName(), this.getClass().getSimpleName())); //$NON-NLS-1$
}
return classLoader;
}
public InputStream getResourceAsStream(Class<?> clazz, String resourcePath) {
ClassLoader classLoader = getClassLoader(clazz);
return getResourceAsStream(classLoader, resourcePath);
}
public InputStream getResourceAsStream(ClassLoader classLoader, String resourcePath) {
if (getOverrideClassloader() != null) {
classLoader = getOverrideClassloader();
}
InputStream in = null;
File root = getPluginDir(classLoader);
if (root != null) {
//can we find it on the filesystem?
File f = new File(root, resourcePath);
if (f.canRead()) {
try {
in = new BufferedInputStream(new FileInputStream(new File(root, resourcePath)));
} catch (FileNotFoundException e) {
Logger.debug(this, "Cannot open stream to resource", e); //$NON-NLS-1$
}
}
//if not on filesystem, ask the classloader
else {
in = classLoader.getResourceAsStream(resourcePath);
if (in == null) {
Logger.debug(this, "Cannot find resource defined by path [" + resourcePath + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
return in;
}
public List<URL> findResources(Class<?> clazz, String namePattern) {
ClassLoader classLoader = getClassLoader(clazz);
return findResources(classLoader, namePattern);
}
public List<URL> findResources(ClassLoader classLoader, String namePattern) {
String dirPattern = "", filePattern = "*"; //$NON-NLS-1$ //$NON-NLS-2$
if (namePattern.contains("/")) { //$NON-NLS-1$
String pattern = namePattern.substring(0, namePattern.lastIndexOf('/'));
if (pattern.length() > 0) {
dirPattern = pattern;
}
pattern = namePattern.substring(namePattern.lastIndexOf('/') + 1, namePattern.length());
if (pattern.length() > 0) {
filePattern = pattern;
}
} else {
filePattern = namePattern;
}
IOFileFilter fileFilter = new WildcardFileFilter(filePattern);
IOFileFilter dirFilter = TrueFileFilter.INSTANCE;
Collection<?> files = FileUtils.listFiles(new File(getPluginDir(classLoader), dirPattern), fileFilter,
dirFilter);
Iterator<?> fileIter = files.iterator();
List<URL> urls = new ArrayList<URL>(files.size());
while (fileIter.hasNext()) {
try {
urls.add(((File) fileIter.next()).toURI().toURL());
} catch (MalformedURLException e) {
Logger.warn(this, "Could not create url", e); //$NON-NLS-1$
}
}
return urls;
}
public ResourceBundle getResourceBundle(Class<?> clazz, String resourcePath) {
ResourceBundle bundle = ResourceBundle.getBundle(resourcePath, LocaleHelper.getLocale(), getClassLoader(clazz));
return bundle;
}
public String getPluginSetting(Class<?> pluginClass, String key) {
return getPluginSetting(pluginClass, key, null);
}
public String getPluginSetting(Class<?> pluginClass, String key, String defaultVal) {
ClassLoader classLoader = getClassLoader(pluginClass);
return PentahoSystem.getSystemSetting(getSystemRelativePluginPath(classLoader) + settingsPath, key, defaultVal);
}
public String getPluginSetting(ClassLoader classLoader, String key, String defaultVal) {
if (getOverrideClassloader() != null) {
classLoader = getOverrideClassloader();
}
return PentahoSystem.getSystemSetting(getSystemRelativePluginPath(classLoader) + settingsPath, key, defaultVal);
}
}