diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationFramework.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationFramework.java
index 0a9d3211d77..6826d64e803 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationFramework.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationFramework.java
@@ -22,22 +22,10 @@
*/
package org.opengrok.indexer.authorization;
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Modifier;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
@@ -47,8 +35,8 @@
import org.opengrok.indexer.configuration.Nameable;
import org.opengrok.indexer.configuration.Project;
import org.opengrok.indexer.configuration.RuntimeEnvironment;
+import org.opengrok.indexer.framework.PluginFramework;
import org.opengrok.indexer.logger.LoggerFactory;
-import org.opengrok.indexer.util.IOUtils;
import org.opengrok.indexer.web.Statistics;
/**
@@ -56,30 +44,26 @@
*
* @author Krystof Tulinger
*/
-public final class AuthorizationFramework {
+public final class AuthorizationFramework extends PluginFramework
* Starting at 0 and increases with every reload.
*/
private long pluginVersion = 0;
- /**
- * Whether to load plugins from class files and jar files.
- */
- private boolean loadClasses = true;
- private boolean loadJars = true;
-
// HTTP session attribute that holds plugin version
private static final String SESSION_VERSION = "opengrok-authorization-session-version";
-
+
/**
* Create a new instance of authorization framework with no plugin
* directory and the default plugin stack.
@@ -110,7 +88,7 @@ public final class AuthorizationFramework {
public AuthorizationFramework() {
this(null);
}
-
+
/**
* Create a new instance of authorization framework with the plugin
* directory and the default plugin stack.
@@ -125,70 +103,12 @@ public AuthorizationFramework(String path) {
* Create a new instance of authorization framework with the plugin
* directory and the plugin stack.
*
- * @param path the plugin directory path
+ * @param path the plugin directory path
* @param stack the top level stack configuration
*/
public AuthorizationFramework(String path, AuthorizationStack stack) {
+ super(IAuthorizationPlugin.class, path);
this.stack = stack;
- setPluginDirectory(path);
- }
-
- /**
- * Get the plugin directory.
- * @return plugin directory file
- */
- public synchronized File getPluginDirectory() {
- return pluginDirectory;
- }
-
- /**
- * Set the plugin directory.
- *
- * @param pluginDirectory the directory
- */
- public synchronized void setPluginDirectory(File pluginDirectory) {
- this.pluginDirectory = pluginDirectory;
- }
-
- /**
- * Set the plugin directory.
- *
- * @param directory the directory path
- */
- public void setPluginDirectory(String directory) {
- setPluginDirectory(directory != null ? new File(directory) : null);
- }
-
- /**
- * Make {@code reload()} search for plugins in class files.
- * @param flag true or false
- */
- public void setLoadClasses(boolean flag) {
- loadClasses = flag;
- }
-
- /**
- * Whether to search for plugins in class files.
- * @return true if enabled, false otherwise
- */
- public boolean isLoadClassesEnabled() {
- return loadClasses;
- }
-
- /**
- * Make {@code reload()} search for plugins in jar files.
- * @param flag true or false
- */
- public void setLoadJars(boolean flag) {
- loadJars = flag;
- }
-
- /**
- * Whether to search for plugins in class files.
- * @return true if enabled, false otherwise
- */
- public boolean isLoadJarsEnabled() {
- return loadJars;
}
/**
@@ -198,7 +118,6 @@ public boolean isLoadJarsEnabled() {
* @param request request object
* @param project project object
* @return true if yes
- *
* @see #checkAll
*/
public boolean isAllowed(HttpServletRequest request, Project project) {
@@ -211,22 +130,22 @@ public boolean isAllowed(HttpServletRequest request, Project project) {
public boolean decision(IAuthorizationPlugin plugin) {
return plugin.isAllowed(request, project);
}
- }, new AuthorizationEntity.PluginSkippingPredicate() {
- @Override
- public boolean shouldSkip(AuthorizationEntity authEntity) {
- // shouldn't skip if there is no setup
- if (authEntity.forProjects().isEmpty() && authEntity.forGroups().isEmpty()) {
- return false;
- }
-
- // shouldn't skip if the project is contained in the setup
- if (authEntity.forProjects().contains(project.getName())) {
- return false;
- }
-
- return true;
- }
- });
+ }, new AuthorizationEntity.PluginSkippingPredicate() {
+ @Override
+ public boolean shouldSkip(AuthorizationEntity authEntity) {
+ // shouldn't skip if there is no setup
+ if (authEntity.forProjects().isEmpty() && authEntity.forGroups().isEmpty()) {
+ return false;
+ }
+
+ // shouldn't skip if the project is contained in the setup
+ if (authEntity.forProjects().contains(project.getName())) {
+ return false;
+ }
+
+ return true;
+ }
+ });
}
/**
@@ -234,9 +153,8 @@ public boolean shouldSkip(AuthorizationEntity authEntity) {
* {@link #checkAll} for more information about invocation order.
*
* @param request request object
- * @param group group object
+ * @param group group object
* @return true if yes
- *
* @see #checkAll
*/
public boolean isAllowed(HttpServletRequest request, Group group) {
@@ -249,43 +167,29 @@ public boolean isAllowed(HttpServletRequest request, Group group) {
public boolean decision(IAuthorizationPlugin plugin) {
return plugin.isAllowed(request, group);
}
- }, new AuthorizationEntity.PluginSkippingPredicate() {
- @Override
- public boolean shouldSkip(AuthorizationEntity authEntity) {
- // shouldn't skip if there is no setup
- if (authEntity.forProjects().isEmpty() && authEntity.forGroups().isEmpty()) {
- return false;
- }
-
- // shouldn't skip if the group is contained in the setup
- return !authEntity.forGroups().contains(group.getName());
- }
- });
- }
+ }, new AuthorizationEntity.PluginSkippingPredicate() {
+ @Override
+ public boolean shouldSkip(AuthorizationEntity authEntity) {
+ // shouldn't skip if there is no setup
+ if (authEntity.forProjects().isEmpty() && authEntity.forGroups().isEmpty()) {
+ return false;
+ }
- /**
- * Return the java canonical name for the plugin class. If the canonical
- * name does not exist it returns the usual java name.
- *
- * @param plugin the plugin
- * @return the class name
- */
- protected String getClassName(IAuthorizationPlugin plugin) {
- if (plugin.getClass().getCanonicalName() != null) {
- return plugin.getClass().getCanonicalName();
- }
- return plugin.getClass().getName();
+ // shouldn't skip if the group is contained in the setup
+ return !authEntity.forGroups().contains(group.getName());
+ }
+ });
}
/**
* Get available plugins.
- *
+ *
* This method and couple of following methods use locking because
*
* So this tries to ensure that there will be no
* {@code ConcurrentModificationException} or other similar exceptions.
*
@@ -313,7 +217,7 @@ public void setStack(AuthorizationStack s) {
/**
* Add an entity into the plugin stack.
*
- * @param stack the stack
+ * @param stack the stack
* @param entity the authorization entity (stack or plugin)
*/
protected void addPlugin(AuthorizationStack stack, AuthorizationEntity entity) {
@@ -326,7 +230,7 @@ protected void addPlugin(AuthorizationStack stack, AuthorizationEntity entity) {
* Add a plug-in into the plug-in stack. This has the same effect as invoking
* addPlugin(stack, IAuthorizationPlugin, REQUIRED).
*
- * @param stack the stack
+ * @param stack the stack
* @param plugin the authorization plug-in
*/
public void addPlugin(AuthorizationStack stack, IAuthorizationPlugin plugin) {
@@ -343,21 +247,21 @@ public void addPlugin(AuthorizationStack stack, IAuthorizationPlugin plugin) {
* The plug-in's load method is NOT invoked at this point The plug-in's load method is NOT invoked at this point
* This has the same effect as invoking
* {@code addPlugin(new AuthorizationEntity(stack, flag,
* getClassName(plugin), plugin)}.
*
- * @param stack the stack
+ * @param stack the stack
* @param plugin the authorization plug-in
- * @param flag the flag for the new plug-in
+ * @param flag the flag for the new plug-in
*/
public void addPlugin(AuthorizationStack stack, IAuthorizationPlugin plugin, AuthControlFlag flag) {
if (stack != null) {
LOGGER.log(Level.WARNING, "Plugin class \"{0}\" was not found in configuration."
- + " Appending the plugin at the end of the list with flag \"{1}\"",
+ + " Appending the plugin at the end of the list with flag \"{1}\"",
new Object[]{getClassName(plugin), flag});
addPlugin(stack, new AuthorizationPlugin(flag, getClassName(plugin), plugin));
}
@@ -372,7 +276,7 @@ public void removeAll() {
unloadAllPlugins(stack);
stack.getStack().clear();
}
-
+
private void removeAll(AuthorizationStack stack) {
unloadAllPlugins(stack);
stack.getStack().clear();
@@ -401,182 +305,28 @@ public void unloadAllPlugins(AuthorizationStack stack) {
}
}
- /**
- * Wrapper around the class loading. Report all exceptions into the log.
- *
- * @param classname full name of the class
- * @return the class implementing the {@link IAuthorizationPlugin} interface
- * or null if there is no such class
- *
- * @see #loadClass(String)
- */
- public IAuthorizationPlugin handleLoadClass(String classname) {
- try {
- return loadClass(classname);
- } catch (ClassNotFoundException ex) {
- LOGGER.log(Level.WARNING, String.format("Class \"%s\" was not found", classname), ex);
- } catch (SecurityException ex) {
- LOGGER.log(Level.WARNING, String.format("Class \"%s\" was found but it is placed in prohibited package: ", classname), ex);
- } catch (InstantiationException ex) {
- LOGGER.log(Level.WARNING, String.format("Class \"%s\" could not be instantiated: ", classname), ex);
- } catch (IllegalAccessException ex) {
- LOGGER.log(Level.WARNING, String.format("Class \"%s\" loader threw an exception: ", classname), ex);
- } catch (Throwable ex) {
- LOGGER.log(Level.WARNING, String.format("Class \"%s\" loader threw an unknown error: ", classname), ex);
- }
- return null;
- }
-
- /**
- * Load a class into JVM with custom class loader. Call a non-parametric
- * constructor to create a new instance of that class.
- *
- *
- * The classes implementing the {@link IAuthorizationPlugin} interface are
- * returned and initialized with a call to a non-parametric constructor.
- *
*
- *
+ * New plugin
* If there is no entry in configuration for this class, the plugin is
* appended to the end of the plugin stack with flag flag
- *
- *
- * Old instances of stack are removed and new list of stack is constructed. + * Old instances in stack are removed and new list of stack is constructed. * Unload and load event is fired on each plugin.
* *@@ -596,46 +346,15 @@ private String getClassName(JarEntry f) { * @see Configuration#getPluginDirectory() */ @SuppressWarnings({"rawtypes", "unchecked"}) - public void reload() { - if (pluginDirectory == null || !pluginDirectory.isDirectory() || !pluginDirectory.canRead()) { - LOGGER.log(Level.WARNING, "Plugin directory not found or not readable: {0}. " - + "All requests allowed.", pluginDirectory); - return; - } + @Override + protected void afterReload() { if (stack == null) { LOGGER.log(Level.WARNING, "Plugin stack not found in configuration: null. All requests allowed."); return; } - LOGGER.log(Level.INFO, "Plugins are being reloaded from {0}", pluginDirectory.getAbsolutePath()); - - // trashing out the old instance of the loaded enables us - // to reload the stack at runtime - loader = (AuthorizationPluginClassLoader) AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - return new AuthorizationPluginClassLoader(pluginDirectory); - } - }); - - AuthorizationStack newLocalStack; - if (this.newStack == null) { - // Clone a new stack not interfering with the current stack. - newLocalStack = getStack().clone(); - } else { - newLocalStack = this.newStack.clone(); - } - - // Load all other possible plugin classes. - if (isLoadClassesEnabled()) { - loadClassFiles(newLocalStack, IOUtils.listFilesRec(pluginDirectory, ".class")); - } - if (isLoadJarsEnabled()) { - loadJarFiles(newLocalStack, IOUtils.listFiles(pluginDirectory, ".jar")); - } - // fire load events - loadAllPlugins(newLocalStack); + loadAllPlugins(loadingStack); AuthorizationStack oldStack; /** @@ -648,8 +367,8 @@ public Object run() { lock.writeLock().lock(); try { oldStack = stack; - stack = newLocalStack; - + stack = loadingStack; + // increase the current plugin version tracked by the framework increasePluginVersion(); } finally { @@ -662,13 +381,14 @@ public Object run() { // clean the old stack removeAll(oldStack); oldStack = null; + loadingStack = null; } /** * Returns the current plugin version in this framework. - * + *
* This number changes with every {@code reload()}. - * + *
* Assumes the {@code lock} is held for reading. * * @return the current version number @@ -679,7 +399,7 @@ private long getPluginVersion() { /** * Changes the plugin version to the next version. - * + *
* Assumes that {@code lock} is held for writing. */ private void increasePluginVersion() { @@ -688,7 +408,7 @@ private void increasePluginVersion() { /** * Is this session marked as invalid? - * + *
* Assumes the {@code lock} is held for reading. * * @param session the request session @@ -698,7 +418,7 @@ private boolean isSessionInvalid(HttpSession session) { if (session.getAttribute(SESSION_VERSION) == null) { return true; } - + long version = (long) session.getAttribute(SESSION_VERSION); return version != getPluginVersion(); @@ -745,20 +465,19 @@ private boolean isSessionInvalid(HttpSession session) { *
* Plugins in the configuration which have not been loaded are skipped.
* - * @param request request object - * @param cache cache - * @param entity entity with name - * @param pluginPredicate predicate to determine the plugin's decision for the request + * @param request request object + * @param cache cache + * @param entity entity with name + * @param pluginPredicate predicate to determine the plugin's decision for the request * @param skippingPredicate predicate to determine if the plugin should be skipped for this request * @return true if yes - * * @see RuntimeEnvironment#getPluginStack() */ @SuppressWarnings("unchecked") private boolean checkAll(HttpServletRequest request, String cache, Nameable entity, AuthorizationEntity.PluginDecisionPredicate pluginPredicate, AuthorizationEntity.PluginSkippingPredicate skippingPredicate) { - + if (stack == null) { return true; } @@ -786,7 +505,7 @@ private boolean checkAll(HttpServletRequest request, String cache, Nameable enti long time = 0; boolean overallDecision = false; - + lock.readLock().lock(); try { // Make sure there is a HTTP session that corresponds to current plugin version. @@ -818,29 +537,29 @@ private boolean checkAll(HttpServletRequest request, String cache, Nameable enti String.format("authorization_of_%s", entity.getName()), time); } - + m.put(entity.getName(), overallDecision); request.setAttribute(cache, m); - + return overallDecision; } /** * Perform the actual check for the entity. - * + *
* Assumes that {@code lock} is held in read mode.
- *
- * @param entity either a project or a group
- * @param pluginPredicate a predicate that decides if the authorization is
- * successful for the given plugin
+ *
+ * @param entity either a project or a group
+ * @param pluginPredicate a predicate that decides if the authorization is
+ * successful for the given plugin
* @param skippingPredicate predicate that decides if given authorization
- * entity should be omitted from the authorization process
+ * entity should be omitted from the authorization process
* @return true if entity is allowed; false otherwise
*/
private boolean performCheck(Nameable entity,
AuthorizationEntity.PluginDecisionPredicate pluginPredicate,
AuthorizationEntity.PluginSkippingPredicate skippingPredicate) {
-
- return stack.isAllowed(entity, pluginPredicate, skippingPredicate);
+
+ return stack.isAllowed(entity, pluginPredicate, skippingPredicate);
}
}
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationPluginClassLoader.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginClassLoader.java
similarity index 87%
rename from opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationPluginClassLoader.java
rename to opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginClassLoader.java
index e3e4a279624..172294c59ed 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationPluginClassLoader.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginClassLoader.java
@@ -17,10 +17,10 @@
* CDDL HEADER END
*/
- /*
+/*
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
*/
-package org.opengrok.indexer.authorization;
+package org.opengrok.indexer.framework;
import java.io.File;
import java.io.FileInputStream;
@@ -37,39 +37,39 @@
import org.opengrok.indexer.logger.LoggerFactory;
/**
- * Class loader for authorization plugins.
+ * Class loader for plugins from .class and .jar files.
*
* @author Krystof Tulinger
*/
-public class AuthorizationPluginClassLoader extends ClassLoader {
+public class PluginClassLoader extends ClassLoader {
@SuppressWarnings("rawtypes")
private final Map
* Order of lookup:
*
* Package blacklist: {@link #PACKAGE_BLACKLIST}.
* Order of lookup:
*
* Package blacklist: {@link #PACKAGE_BLACKLIST}.
+ * The classes implementing/extending the {@code PluginType} type are
+ * returned and initialized with a call to a non-parametric constructor.
+ *
+ * Each class is loaded with {@link #handleLoadClass(String)} which
+ * delegates the loading to the custom class loader
+ * {@link #loadClass(String)}.
+ *
+ * @param jarfiles list of jar files containing java classes
+ * @see #handleLoadClass(String)
+ * @see #loadClass(String)
+ */
+ private void loadJarFiles(List
+ * When this is invoked, all plugins has been loaded into the memory and for each available plugin
+ * the {@link #classLoaded(Object)} was invoked.
+ */
+ protected abstract void afterReload();
+
+ /**
+ * Calling this function forces the framework to reload the plugins.
+ *
+ *
+ * Plugins are taken from the pluginDirectory.
*
- *
+ *
* Classes whitelist: {@link #CLASS_WHITELIST}.
*
* @param name class name
* @return loaded class or null
* @throws ClassNotFoundException if class is not found
- * @throws SecurityException if the loader cannot access the class
+ * @throws SecurityException if the loader cannot access the class
*/
@Override
public Class> loadClass(String name) throws ClassNotFoundException, SecurityException {
@@ -202,7 +202,7 @@ public Class> loadClass(String name) throws ClassNotFoundException, SecurityEx
/**
* Loads the class with given name.
- *
+ *
*
- *
+ *
* Classes whitelist: {@link #CLASS_WHITELIST}.
*
- * @param name class name
+ * @param name class name
* @param resolveIt if the class should be resolved
* @return loaded class or null
* @throws ClassNotFoundException if class is not found
- * @throws SecurityException if the loader cannot access the class
+ * @throws SecurityException if the loader cannot access the class
*/
@Override
@SuppressWarnings("rawtypes")
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java
new file mode 100644
index 00000000000..e6586a54447
--- /dev/null
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java
@@ -0,0 +1,381 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * See LICENSE.txt included in this distribution for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at LICENSE.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
+ */
+package org.opengrok.indexer.framework;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.opengrok.indexer.authorization.IAuthorizationPlugin;
+import org.opengrok.indexer.logger.LoggerFactory;
+import org.opengrok.indexer.util.IOUtils;
+
+/**
+ * Plugin framework for plugins of type {@code PluginType}.
+ *
+ * @author Krystof Tulinger
+ */
+public abstract class PluginFramework