All Downloads are FREE. Search and download functionalities are using the official Maven repository.

grails.plugins.DefaultGrailsPluginManager Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2004-2005 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package grails.plugins;

import grails.util.Environment;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClassRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;
import grails.core.GrailsApplication;
import org.grails.core.io.CachingPathMatchingResourcePatternResolver;
import org.grails.plugins.*;
import org.grails.spring.DefaultRuntimeSpringConfiguration;
import org.grails.spring.RuntimeSpringConfiguration;
import org.grails.core.exceptions.GrailsConfigurationException;
import org.grails.io.support.GrailsResourceUtils;
import grails.plugins.exceptions.PluginException;
import grails.core.support.ParentApplicationContextAware;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.IOGroovyMethods;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.util.*;

/**
 * 

Handles the loading and management of plug-ins in the Grails system. * A plugin is just like a normal Grails application except that it contains a file ending * in *Plugin.groovy in the root of the directory. *

*

A Plugin class is a Groovy class that has a version and optionally closures * called doWithSpring, doWithContext and doWithWebDescriptor *

*

The doWithSpring closure uses the BeanBuilder syntax (@see grails.spring.BeanBuilder) to * provide runtime configuration of Grails via Spring *

*

The doWithContext closure is called after the Spring ApplicationContext is built and accepts * a single argument (the ApplicationContext) *

*

The doWithWebDescriptor uses mark-up building to provide additional functionality to the web.xml * file *

*

Example: *

 * class ClassEditorGrailsPlugin {
 *      def version = '1.1'
 *      def doWithSpring = { application ->
 *          classEditor(org.springframework.beans.propertyeditors.ClassEditor, application.classLoader)
 *      }
 * }
 * 
*

*

A plugin can also define "dependsOn" and "evict" properties that specify what plugins the plugin * depends on and which ones it is incompatible with and should evict * * @author Graeme Rocher * @since 0.4 */ public class DefaultGrailsPluginManager extends AbstractGrailsPluginManager { private static final Log LOG = LogFactory.getLog(DefaultGrailsPluginManager.class); protected static final Class[] COMMON_CLASSES = { Boolean.class, Byte.class, Character.class, Class.class, Double.class, Float.class, Integer.class, Long.class, Number.class, Short.class, String.class, BigInteger.class, BigDecimal.class, URL.class, URI.class }; private List delayedLoadPlugins = new LinkedList(); private ApplicationContext parentCtx; private PathMatchingResourcePatternResolver resolver; private Map delayedEvictions = new HashMap(); private Map> pluginToObserverMap = new HashMap>(); private PluginFilter pluginFilter; private static final String GRAILS_PLUGIN_SUFFIX = "GrailsPlugin"; private List userPlugins = new ArrayList(); public DefaultGrailsPluginManager(String resourcePath, GrailsApplication application) { super(application); Assert.notNull(application, "Argument [application] cannot be null!"); resolver = CachingPathMatchingResourcePatternResolver.INSTANCE; try { pluginResources = resolver.getResources(resourcePath); } catch (IOException ioe) { LOG.debug("Unable to load plugins for resource path " + resourcePath, ioe); } //corePlugins = new PathMatchingResourcePatternResolver().getResources("classpath:org/codehaus/groovy/grails/**/plugins/**GrailsPlugin.groovy"); this.application = application; setPluginFilter(); } public DefaultGrailsPluginManager(String[] pluginResources, GrailsApplication application) { super(application); resolver = CachingPathMatchingResourcePatternResolver.INSTANCE; List resourceList = new ArrayList(); for (String resourcePath : pluginResources) { try { resourceList.addAll(Arrays.asList(resolver.getResources(resourcePath))); } catch (IOException ioe) { LOG.debug("Unable to load plugins for resource path " + resourcePath, ioe); } } this.pluginResources = resourceList.toArray(new Resource[resourceList.size()]); this.application = application; setPluginFilter(); } public DefaultGrailsPluginManager(Class[] plugins, GrailsApplication application) { super(application); pluginClasses = plugins; resolver = CachingPathMatchingResourcePatternResolver.INSTANCE; //this.corePlugins = new PathMatchingResourcePatternResolver().getResources("classpath:org/codehaus/groovy/grails/**/plugins/**GrailsPlugin.groovy"); this.application = application; setPluginFilter(); } public DefaultGrailsPluginManager(Resource[] pluginFiles, GrailsApplication application) { super(application); resolver = CachingPathMatchingResourcePatternResolver.INSTANCE; pluginResources = pluginFiles; this.application = application; setPluginFilter(); } public DefaultGrailsPluginManager(GrailsApplication application) { super(application); } public GrailsPlugin[] getUserPlugins() { return userPlugins.toArray(new GrailsPlugin[userPlugins.size()]); } private void setPluginFilter() { pluginFilter = new PluginFilterRetriever().getPluginFilter(application.getConfig()); } /** * @deprecated Will be removed in a future version of Grails */ @Deprecated public void startPluginChangeScanner() { // do nothing } /** * @deprecated Will be removed in a future version of Grails */ @Deprecated public void stopPluginChangeScanner() { // do nothing } public void refreshPlugin(String name) { if (hasGrailsPlugin(name)) { getGrailsPlugin(name).refresh(); } } public Collection getPluginObservers(GrailsPlugin plugin) { Assert.notNull(plugin, "Argument [plugin] cannot be null"); Collection c = pluginToObserverMap.get(plugin.getName()); // Add any wildcard observers. Collection wildcardObservers = pluginToObserverMap.get("*"); if (wildcardObservers != null) { if (c != null) { c.addAll(wildcardObservers); } else { c = wildcardObservers; } } if (c != null) { // Make sure this plugin is not observing itself! c.remove(plugin); return c; } return Collections.emptySet(); } @SuppressWarnings("rawtypes") public void informObservers(String pluginName, Map event) { GrailsPlugin plugin = getGrailsPlugin(pluginName); if (plugin == null) { return; } if(!plugin.isEnabled(applicationContext.getEnvironment().getActiveProfiles())) return; for (GrailsPlugin observingPlugin : getPluginObservers(plugin)) { if(!observingPlugin.isEnabled(applicationContext.getEnvironment().getActiveProfiles())) continue; observingPlugin.notifyOfEvent(event); } } /* (non-Javadoc) * @see grails.plugins.GrailsPluginManager#loadPlugins() */ public void loadPlugins() throws PluginException { if (initialised) { return; } ClassLoader gcl = application.getClassLoader(); attemptLoadPlugins(gcl); if (!delayedLoadPlugins.isEmpty()) { loadDelayedPlugins(); } if (!delayedEvictions.isEmpty()) { processDelayedEvictions(); } pluginList = sortPlugins(pluginList); initializePlugins(); initialised = true; } protected List sortPlugins(List toSort) { /* http://en.wikipedia.org/wiki/Topological_sorting * * L ← Empty list that will contain the sorted nodes S ← Set of all nodes function visit(node n) if n has not been visited yet then mark n as visited for each node m with an edge from n to m do visit(m) add n to L for each node n in S do visit(n) */ List sortedPlugins = new ArrayList(toSort.size()); Set visitedPlugins = new HashSet(); Map> loadOrderDependencies = resolveLoadDependencies(toSort); for (GrailsPlugin plugin : toSort) { visitTopologicalSort(plugin, sortedPlugins, visitedPlugins, loadOrderDependencies); } return sortedPlugins; } protected Map> resolveLoadDependencies(List plugins) { Map> loadOrderDependencies = new HashMap>(); for (GrailsPlugin plugin : plugins) { if(plugin.getLoadAfterNames() != null) { List loadDepsForPlugin = loadOrderDependencies.get(plugin); if(loadDepsForPlugin==null) { loadDepsForPlugin = new ArrayList(); loadOrderDependencies.put(plugin, loadDepsForPlugin); } for(String pluginName : plugin.getLoadAfterNames()) { GrailsPlugin loadAfterPlugin = getGrailsPlugin(pluginName); if(loadAfterPlugin != null) { loadDepsForPlugin.add(loadAfterPlugin); } } } for (String loadBefore : plugin.getLoadBeforeNames()) { GrailsPlugin loadBeforePlugin = getGrailsPlugin(loadBefore); if(loadBeforePlugin != null) { List loadDepsForPlugin = loadOrderDependencies.get(loadBeforePlugin); if(loadDepsForPlugin==null) { loadDepsForPlugin = new ArrayList(); loadOrderDependencies.put(loadBeforePlugin, loadDepsForPlugin); } loadDepsForPlugin.add(plugin); } } } return loadOrderDependencies; } private void visitTopologicalSort(GrailsPlugin plugin, List sortedPlugins, Set visitedPlugins, Map> loadOrderDependencies) { if(plugin != null && !visitedPlugins.contains(plugin)) { visitedPlugins.add(plugin); List loadDepsForPlugin = loadOrderDependencies.get(plugin); if(loadDepsForPlugin != null) { for(GrailsPlugin dependentPlugin : loadDepsForPlugin) { visitTopologicalSort(dependentPlugin, sortedPlugins, visitedPlugins, loadOrderDependencies); } } sortedPlugins.add(plugin); } } private void attemptLoadPlugins(ClassLoader gcl) { // retrieve load core plugins first List grailsCorePlugins = loadCorePlugins ? findCorePlugins() : new ArrayList(); List grailsUserPlugins = findUserPlugins(gcl); userPlugins = grailsUserPlugins; List allPlugins = new ArrayList (grailsCorePlugins); allPlugins.addAll(grailsUserPlugins); //filtering applies to user as well as core plugins List filteredPlugins = getPluginFilter().filterPluginList(allPlugins); //make sure core plugins are loaded first List orderedCorePlugins = new ArrayList (); List orderedUserPlugins = new ArrayList (); for (GrailsPlugin plugin : filteredPlugins) { if (grailsCorePlugins != null) { if (grailsCorePlugins.contains(plugin)) { orderedCorePlugins.add(plugin); } else { orderedUserPlugins.add(plugin); } } } List orderedPlugins = new ArrayList (); orderedPlugins.addAll(orderedCorePlugins); orderedPlugins.addAll(orderedUserPlugins); for (GrailsPlugin plugin : orderedPlugins) { attemptPluginLoad(plugin); } } private List findCorePlugins() { CorePluginFinder finder = new CorePluginFinder(application); finder.setParentApplicationContext(parentCtx); List grailsCorePlugins = new ArrayList(); final Class[] corePluginClasses = finder.getPluginClasses(); for (Class pluginClass : corePluginClasses) { if (pluginClass != null && !Modifier.isAbstract(pluginClass.getModifiers()) && pluginClass != DefaultGrailsPlugin.class) { final BinaryGrailsPluginDescriptor binaryDescriptor = finder.getBinaryDescriptor(pluginClass); GrailsPlugin plugin; if (binaryDescriptor != null) { plugin = createBinaryGrailsPlugin(pluginClass, binaryDescriptor); } else { plugin = createGrailsPlugin(pluginClass); } plugin.setApplicationContext(applicationContext); grailsCorePlugins.add(plugin); } } return grailsCorePlugins; } private GrailsPlugin createBinaryGrailsPlugin(Class pluginClass, BinaryGrailsPluginDescriptor binaryDescriptor) { return new BinaryGrailsPlugin(pluginClass, binaryDescriptor, application); } protected GrailsPlugin createGrailsPlugin(Class pluginClass) { return new DefaultGrailsPlugin(pluginClass, application); } protected GrailsPlugin createGrailsPlugin(Class pluginClass, Resource resource) { return new DefaultGrailsPlugin(pluginClass, resource, application); } private List findUserPlugins(ClassLoader gcl) { List grailsUserPlugins = new ArrayList(); LOG.info("Attempting to load [" + pluginResources.length + "] user defined plugins"); for (Resource r : pluginResources) { Class pluginClass = loadPluginClass(gcl, r); if (isGrailsPlugin(pluginClass)) { GrailsPlugin plugin = createGrailsPlugin(pluginClass, r); //attemptPluginLoad(plugin); grailsUserPlugins.add(plugin); } else { LOG.warn("Class [" + pluginClass + "] not loaded as plug-in. Grails plug-ins must end with the convention 'GrailsPlugin'!"); } } for (Class pluginClass : pluginClasses) { if (isGrailsPlugin(pluginClass)) { GrailsPlugin plugin = createGrailsPlugin(pluginClass); //attemptPluginLoad(plugin); grailsUserPlugins.add(plugin); } else { LOG.warn("Class [" + pluginClass + "] not loaded as plug-in. Grails plug-ins must end with the convention 'GrailsPlugin'!"); } } return grailsUserPlugins; } private boolean isGrailsPlugin(Class pluginClass) { return pluginClass != null && pluginClass.getName().endsWith(GRAILS_PLUGIN_SUFFIX); } private void processDelayedEvictions() { for (Map.Entry entry : delayedEvictions.entrySet()) { GrailsPlugin plugin = entry.getKey(); for (String pluginName : entry.getValue()) { evictPlugin(plugin, pluginName); } } } private void initializePlugins() { for (Object plugin : plugins.values()) { if (plugin instanceof ApplicationContextAware) { ((ApplicationContextAware) plugin).setApplicationContext(applicationContext); } } } /** * This method will attempt to load that plug-ins not loaded in the first pass */ private void loadDelayedPlugins() { while (!delayedLoadPlugins.isEmpty()) { GrailsPlugin plugin = delayedLoadPlugins.remove(0); if (areDependenciesResolved(plugin)) { if (!hasValidPluginsToLoadBefore(plugin)) { registerPlugin(plugin); } else { delayedLoadPlugins.add(plugin); } } else { // ok, it still hasn't resolved the dependency after the initial // load of all plugins. All hope is not lost, however, so lets first // look inside the remaining delayed loads before giving up boolean foundInDelayed = false; for (GrailsPlugin remainingPlugin : delayedLoadPlugins) { if (isDependentOn(plugin, remainingPlugin)) { foundInDelayed = true; break; } } if (foundInDelayed) { delayedLoadPlugins.add(plugin); } else { failedPlugins.put(plugin.getName(), plugin); LOG.warn("WARNING: Plugin [" + plugin.getName() + "] cannot be loaded because its dependencies [" + DefaultGroovyMethods.inspect(plugin.getDependencyNames()) + "] cannot be resolved"); } } } } private boolean hasValidPluginsToLoadBefore(GrailsPlugin plugin) { String[] loadAfterNames = plugin.getLoadAfterNames(); for (Object delayedLoadPlugin : delayedLoadPlugins) { GrailsPlugin other = (GrailsPlugin) delayedLoadPlugin; for (String name : loadAfterNames) { if (other.getName().equals(name)) { return hasDelayedDependencies(other) || areDependenciesResolved(other); } } } return false; } private boolean hasDelayedDependencies(GrailsPlugin other) { String[] dependencyNames = other.getDependencyNames(); for (String dependencyName : dependencyNames) { for (GrailsPlugin grailsPlugin : delayedLoadPlugins) { if (grailsPlugin.getName().equals(dependencyName)) return true; } } return false; } /** * Checks whether the first plugin is dependant on the second plugin. * * @param plugin The plugin to check * @param dependency The plugin which the first argument may be dependant on * @return true if it is */ private boolean isDependentOn(GrailsPlugin plugin, GrailsPlugin dependency) { for (String name : plugin.getDependencyNames()) { String requiredVersion = plugin.getDependentVersion(name); if (name.equals(dependency.getName()) && GrailsVersionUtils.isValidVersion(dependency.getVersion(), requiredVersion)) return true; } return false; } private boolean areDependenciesResolved(GrailsPlugin plugin) { for (String name : plugin.getDependencyNames()) { if (!hasGrailsPlugin(name, plugin.getDependentVersion(name))) { return false; } } return true; } /** * Returns true if there are no plugins left that should, if possible, be loaded before this plugin. * * @param plugin The plugin * @return true if there are */ private boolean areNoneToLoadBefore(GrailsPlugin plugin) { for (String name : plugin.getLoadAfterNames()) { if (getGrailsPlugin(name) == null) { return false; } } return true; } private Class loadPluginClass(ClassLoader cl, Resource r) { Class pluginClass; if (cl instanceof GroovyClassLoader) { try { if (LOG.isInfoEnabled()) { LOG.info("Parsing & compiling " + r.getFilename()); } pluginClass = ((GroovyClassLoader)cl).parseClass(IOGroovyMethods.getText(r.getInputStream(), "UTF-8")); } catch (CompilationFailedException e) { throw new PluginException("Error compiling plugin [" + r.getFilename() + "] " + e.getMessage(), e); } catch (IOException e) { throw new PluginException("Error reading plugin [" + r.getFilename() + "] " + e.getMessage(), e); } } else { String className = null; try { className = GrailsResourceUtils.getClassName(r.getFile().getAbsolutePath()); } catch (IOException e) { throw new PluginException("Cannot find plugin class [" + className + "] resource: [" + r.getFilename()+"]", e); } try { pluginClass = Class.forName(className, true, cl); } catch (ClassNotFoundException e) { throw new PluginException("Cannot find plugin class [" + className + "] resource: [" + r.getFilename()+"]", e); } } return pluginClass; } /** * Attempts to load a plugin based on its dependencies. If a plugin's dependencies cannot be resolved * it will add it to the list of dependencies to be resolved later. * * @param plugin The plugin */ private void attemptPluginLoad(GrailsPlugin plugin) { if (areDependenciesResolved(plugin) && areNoneToLoadBefore(plugin)) { registerPlugin(plugin); } else { delayedLoadPlugins.add(plugin); } } private void registerPlugin(GrailsPlugin plugin) { if (!canRegisterPlugin(plugin)) { if (LOG.isInfoEnabled()) { LOG.info("Grails plugin " + plugin + " is disabled and was not loaded"); } return; } if (LOG.isInfoEnabled()) { LOG.info("Grails plug-in [" + plugin.getName() + "] with version [" + plugin.getVersion() + "] loaded successfully"); } if (plugin instanceof ParentApplicationContextAware) { ((ParentApplicationContextAware) plugin).setParentApplicationContext(parentCtx); } plugin.setManager(this); String[] evictionNames = plugin.getEvictionNames(); if (evictionNames.length > 0) { delayedEvictions.put(plugin, evictionNames); } String[] observedPlugins = plugin.getObservedPluginNames(); for (String observedPlugin : observedPlugins) { Set observers = pluginToObserverMap.get(observedPlugin); if (observers == null) { observers = new HashSet(); pluginToObserverMap.put(observedPlugin, observers); } observers.add(plugin); } pluginList.add(plugin); plugins.put(plugin.getName(), plugin); classNameToPluginMap.put(plugin.getPluginClass().getName(), plugin); } protected boolean canRegisterPlugin(GrailsPlugin plugin) { Environment environment = Environment.getCurrent(); return plugin.isEnabled() && plugin.supportsEnvironment(environment); } protected void evictPlugin(GrailsPlugin evictor, String evicteeName) { GrailsPlugin pluginToEvict = plugins.get(evicteeName); if (pluginToEvict != null) { pluginList.remove(pluginToEvict); plugins.remove(pluginToEvict.getName()); if (LOG.isInfoEnabled()) { LOG.info("Grails plug-in " + pluginToEvict + " was evicted by " + evictor); } } } private boolean hasGrailsPlugin(String name, String version) { return getGrailsPlugin(name, version) != null; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; for (GrailsPlugin plugin : pluginList) { plugin.setApplicationContext(applicationContext); } } public void setParentApplicationContext(ApplicationContext parent) { parentCtx = parent; } /** * @deprecated Replaced by agent-based reloading, will be removed in a future version of Grails */ @Deprecated public void checkForChanges() { // do nothing } public void reloadPlugin(GrailsPlugin plugin) { plugin.doArtefactConfiguration(); RuntimeSpringConfiguration springConfig = new DefaultRuntimeSpringConfiguration(parentCtx); doRuntimeConfiguration(plugin.getName(), springConfig); springConfig.registerBeansWithContext((GenericApplicationContext)applicationContext); plugin.doWithApplicationContext(applicationContext); plugin.doWithDynamicMethods(applicationContext); } @Override public void setApplication(GrailsApplication application) { Assert.notNull(application, "Argument [application] cannot be null"); this.application = application; for (GrailsPlugin plugin : pluginList) { plugin.setApplication(application); } } @Override public void doDynamicMethods() { checkInitialised(); // remove common meta classes just to be sure MetaClassRegistry registry = GroovySystem.getMetaClassRegistry(); for (Class COMMON_CLASS : COMMON_CLASSES) { registry.removeMetaClass(COMMON_CLASS); } for (GrailsPlugin plugin : pluginList) { if (plugin.supportsCurrentScopeAndEnvironment()) { try { plugin.doWithDynamicMethods(applicationContext); } catch (Throwable t) { throw new GrailsConfigurationException("Error configuring dynamic methods for plugin " + plugin + ": " + t.getMessage(), t); } } } } public void setPluginFilter(PluginFilter pluginFilter) { this.pluginFilter = pluginFilter; } private PluginFilter getPluginFilter() { if (pluginFilter == null) { pluginFilter = new IdentityPluginFilter(); } return pluginFilter; } List getPluginList() { return Collections.unmodifiableList(pluginList); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy