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

com.servicerocket.confluence.randombits.conveyor.impl.DefaultOverrideManager Maven / Gradle / Ivy

There is a newer version: 2.5.12
Show newest version
package com.servicerocket.confluence.randombits.conveyor.impl;

import com.atlassian.confluence.plugin.descriptor.PluginAwareActionConfig;
import com.atlassian.plugin.Plugin;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.predicate.PluginPredicate;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.config.Configuration;
import com.opensymphony.xwork.config.ConfigurationManager;
import com.opensymphony.xwork.config.entities.ActionConfig;
import com.opensymphony.xwork.config.entities.PackageConfig;
import com.servicerocket.confluence.randombits.conveyor.OverrideManager;
import com.servicerocket.confluence.randombits.conveyor.Receipt;
import org.apache.commons.lang.StringUtils;
import com.servicerocket.confluence.randombits.conveyor.ConveyorException;
import com.servicerocket.confluence.randombits.conveyor.xwork.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import java.util.*;

/**
 * The default implementation of {@link OverrideManager}.
 */
public class DefaultOverrideManager implements OverrideManager, InitializingBean, DisposableBean {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultOverrideManager.class);

    private Map overriddenPackages;

    private final Plugin plugin;

    private int returningReceipts;

    private List receipts;

    public DefaultOverrideManager(PluginAccessor pluginAccessor) {
        this.plugin = findPlugin(pluginAccessor);
        overriddenPackages = new HashMap<>();
        receipts = new ArrayList<>();
    }

    private Plugin findPlugin(PluginAccessor pluginAccessor) {
        for (Plugin plugin : pluginAccessor.getPlugins(new PluginPredicate() {
            public boolean matches(Plugin plugin) {
                try {
                    Class loadedClass = plugin.loadClass(DefaultOverrideManager.class.getName(), null);
                    Class myClass = DefaultOverrideManager.class;
                    return loadedClass == myClass;
                } catch (ClassNotFoundException e) {
                    return false;
                }
            }
        })) {
            return plugin;
        }
        return null;
    }

    public OverridePackageReceipt overridePackage(OverridingPackageConfig overridingPackage) throws ConveyorException {

        String name = overridingPackage.getName();
        String namespace = overridingPackage.getNamespace();

        LOG.debug("Overriding '" + name + "' package with '" + namespace + "' namespace.");

        OverriddenPackageConfig overriddenPackage = asOverriddenPackageConfig(name, namespace);

        // Next, add the override package.
        addOverridingPackage(overriddenPackage, overridingPackage);

        // Lastly, we make sure the overridden package is in the current configuration.
        // It's done here so to avoid having to rollback if there was an error earlier.
        ConfigurationManager.getConfiguration().addPackageConfig(name, overriddenPackage);
        overriddenPackages.put(name, overriddenPackage);
        rebuildRuntimeConfiguration();

        return keepReceipt(new OverridePackageReceipt(overriddenPackage, overridingPackage, this));
    }

    private void addOverridingPackage(OverriddenPackageConfig overriddenPackage, OverridingPackageConfig overridingPackage) throws ConveyorException {
        String name = overriddenPackage.getName();
        String namespace = overriddenPackage.getNamespace();

        try {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Attempting to override the '" + name + "' package.");
            }

            // First, we check the packages match.
            if (!StringUtils.equals(name, overridingPackage.getName())) {
                throw new ConveyorException("Cannot override a package named '" + name + "' with an override package named '" + overridingPackage.getName() + "'.");
            }

            if (!StringUtils.equals(namespace, overridingPackage.getNamespace())) {
                throw new ConveyorException("The '" + name + "' package has a namespace of '" + namespace
                        + "' an cannot be overridden with a package with a different namespace of '" + overridingPackage.getNamespace());
            }

            // Check that my plugin is a dependency of the overriding package's plugin
            String myPluginKey = getPackagePluginKey(overriddenPackage.getOriginalPackage());
            if (myPluginKey != null && !overridingPackage.getPlugin().getRequiredPlugins().contains(myPluginKey)) {
                throw new ConveyorException("The overriding plugin (" + overridingPackage.getPlugin().getKey()
                        + ") must depend on the original plugin (" + myPluginKey + ") to be allowed to override its packages.");
            }

            // Next, we copy the details, if possible.
            copyPackageDetails(overridingPackage, overriddenPackage);

            overrideActions(overriddenPackage, overridingPackage.getOverridingActionsMap());

            overriddenPackage.getOverridingPackageConfigs().add(overridingPackage);

            if (LOG.isDebugEnabled()) {
                LOG.debug("Completed overriding package '" + name + "' with namespace of '" + namespace + "'.");
            }

        } catch (ConveyorException e) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("There was a problem while overriding the '" + name + "' package, reverting: " + e.getMessage());
            }

            // Revert if there is a failure
            revertOverridingPackage(overriddenPackage, overridingPackage);
            throw e;
        }

    }

    private void overrideActions(OverriddenPackageConfig overriddenPackage, Map> overridingActionsMap) throws ConveyorException {
        for (Map.Entry> entry : overridingActionsMap.entrySet()) {

            String actionName = entry.getKey();

            if (LOG.isDebugEnabled()) {
                LOG.debug("Attempting to override the '" + actionName + "' action in the '" + overriddenPackage.getName() + "' package.");
            }

            OverriddenActionConfig overriddenAction = asOverriddenActionConfig(overriddenPackage, actionName);

            // Now, initialise the overriding actions and hook them up.
            Collection overridingActionLinks = overriddenAction.getOverridingActions();

            for (OverridingActionConfig overridingAction : entry.getValue()) {
                if (hasExistingKey(overridingAction, overridingActionLinks)) {
                    throw new ConveyorException("An action-override with the key of '" + overridingAction.getKey()
                            + "' already exists for the '" + actionName + "' in the '" + overriddenPackage.getName()
                            + "' package. Please select a unique key.");
                }
                String originalPluginKey = getActionPluginKey(overriddenAction.getOriginalActionConfig());
                if (overridingAction.getPlugin().getDependencies().getAll().contains(originalPluginKey)) {
                    throw new ConveyorException("The '" + overridingAction.getKey() + "' in the '" + overridingAction.getPackageName()
                            + "' package cannot override the '" + actionName + "' action because the plugin must depend on the '"
                            + originalPluginKey + "' plugin.");
                }

                String alias = findActionAlias(overriddenPackage, actionName);

                OverridingActionLink actionLink = new OverridingActionLink(alias, overridingAction);
                overridingActionLinks.add(actionLink);

                // TODO: Load the admin-specified weight, if present.

                // Add copy of the action as a transient
                TransientActionConfig copiedAction;
                // Apply the original config if inherited.
                if (overridingAction.isInherited()) {
                    copiedAction = new TransientActionConfig(actionName, overriddenAction.getOriginalActionConfig(), overridingAction);
                } else {
                    copiedAction = new TransientActionConfig(actionName, overridingAction);
                }

                // Add it to the main config via the alias.
                overriddenPackage.addActionConfig(alias, copiedAction);
            }

            overriddenAction.sortOverridingActions();

            if (LOG.isDebugEnabled()) {
                LOG.debug("Completed overriding the '" + actionName + "' action in the '" + overriddenPackage.getName() + "' package.");
            }
        }
    }

    private static String findActionAlias(PackageConfig packageConfig, String actionName) {
        Map actionConfigs = packageConfig.getActionConfigs();

        int i = 1;
        while (actionConfigs.containsKey(getAlias(actionName, i))) {
            i++;
        }

        return getAlias(actionName, i);
    }


    private static String getAlias(String actionName, int i) {
        return actionName + "_" + i;
    }

    private boolean hasExistingKey(OverridingActionConfig overridingAction, Collection overridingActionLinks) {
        for (OverridingActionLink link : overridingActionLinks) {
            if (StringUtils.equals(link.getActionConfig().getKey(), overridingAction.getKey())) {
                return true;
            }
        }
        return false;
    }

    private OverriddenActionConfig asOverriddenActionConfig(OverriddenPackageConfig overriddenPackage, String actionName) throws ConveyorException {
        OverriddenActionConfig overriddenAction;
        ActionConfig originalAction = overriddenPackage.getActionConfig(actionName);

        // Find the overridden action, or fail if there is a problem.
        if (originalAction instanceof OverriddenActionConfig) {
            overriddenAction = (OverriddenActionConfig) originalAction;
        } else if (originalAction instanceof TransientActionConfig) {
            throw new ConveyorException("The action named '" + actionName + "' in the '"
                    + overriddenPackage.getName()
                    + "' package cannot be overridden because it's a temporary action.");
        } else if (originalAction != null) {
            // convert it to an OverriddenActionConfig.
            String alias = findActionAlias(overriddenPackage, actionName);
            overriddenAction = new OverriddenActionConfig(getConveyorPlugin(), alias, originalAction);
            // Clone the original and add it with the alias
            TransientActionConfig cloneAction = new TransientActionConfig(actionName, originalAction, getConveyorPlugin());
            overriddenPackage.addActionConfig(alias, cloneAction);
            // Replace the original
            overriddenPackage.addActionConfig(actionName, overriddenAction);

            // Add it to the list of overridden actions.
            overriddenPackage.addOverriddenAction(actionName, overriddenAction);
        } else {
            throw new ConveyorException("No action named '" + actionName + "' is present in the '" + overriddenPackage.getName() + "' package to override.");
        }

        return overriddenAction;
    }


    private void copyPackageDetails(PackageConfig sourcePackage, PackageConfig targetPackage) throws ConveyorException {
        try {
            String name = targetPackage.getName();
            copyMapValues(sourcePackage.getResultTypeConfigs(), targetPackage.getResultTypeConfigs(), "Result Type Config", name);
            copyMapValues(sourcePackage.getInterceptorConfigs(), targetPackage.getInterceptorConfigs(), "Interceptor Config", name);
            copyMapValues(sourcePackage.getGlobalResultConfigs(), targetPackage.getGlobalResultConfigs(), "Global Result Config", name);
            copyMapValues(sourcePackage.getActionConfigs(), targetPackage.getActionConfigs(), "Action Config", name);
        } catch (ConveyorException e) {
            revertPackage(sourcePackage, targetPackage);
            throw e;
        }
    }


    public void revertOverridingPackage(OverriddenPackageConfig overriddenPackage, OverridingPackageConfig overridingPackage) {
        revertActions(overriddenPackage, overridingPackage.getOverridingActionsMap());
        revertPackage(overriddenPackage, overridingPackage);
        overriddenPackage.getOverridingPackageConfigs().remove(overridingPackage);

        if (overriddenPackage.getOverridingPackageConfigs().isEmpty()) {
            // Revert to the original package
            ConfigurationManager.getConfiguration().addPackageConfig(overriddenPackage.getName(), overriddenPackage.getOriginalPackage());
            overriddenPackages.remove(overriddenPackage.getName());
        }
    }

    private void revertActions(OverriddenPackageConfig overriddenPackage, Map> overridingActionsMap) {
        for (String actionName : overridingActionsMap.keySet()) {
            ActionConfig targetAction = overriddenPackage.getActionConfig(actionName);
            if (targetAction instanceof OverriddenActionConfig) {
                OverriddenActionConfig overriddenAction = (OverriddenActionConfig) targetAction;

                // Remove any matching overriding actions
                List overridingActions = overridingActionsMap.get(actionName);
                for (OverridingActionConfig overridingAction : overridingActions) {
                    // Loop through the existing links.
                    Iterator i = overriddenAction.getOverridingActions().iterator();
                    while (i.hasNext()) {
                        OverridingActionLink link = i.next();
                        if (link.getKey().equals(overridingAction.getKey())) {
                            // Remove the aliased link
                            removeActionConfig(overriddenPackage, link.getAlias());
                            i.remove();
                        }
                    }
                }
                // Now check if we should to back to the original action
                if (overriddenAction.getOverridingActions().isEmpty()) {
                    removeActionConfig(overriddenPackage, overriddenAction.getOriginalActionAlias());
                    removeActionConfig(overriddenPackage, actionName);
                    overriddenPackage.getOverriddenActionConfigs().remove(actionName);
                    overriddenPackage.addActionConfig(actionName, overriddenAction.getOriginalActionConfig());
                }
            }
        }
    }

    private void removeActionConfig(PackageConfig packageConfig, String actionName) {
        packageConfig.getActionConfigs().remove(actionName);
        if (packageConfig instanceof OverriddenPackageConfig) {
            removeActionAliases((OverriddenPackageConfig) packageConfig, actionName);
        }
    }

    private void removeActionAliases(OverriddenPackageConfig overriddenPackage, String actionName) {
        Set aliasSet = overriddenPackage.getAliases().get(actionName);
        if (aliasSet != null) {
            for (String alias : aliasSet) {
                removeActionConfig(overriddenPackage, alias);
            }
        }
    }

    private void revertPackage(PackageConfig sourcePackage, PackageConfig targetPackage) {
        removeMapValues(sourcePackage.getResultTypeConfigs(), targetPackage.getResultTypeConfigs());
        removeMapValues(sourcePackage.getInterceptorConfigs(), targetPackage.getInterceptorConfigs());
        removeMapValues(sourcePackage.getGlobalResultConfigs(), targetPackage.getGlobalResultConfigs());

        Map actionConfigs = sourcePackage.getActionConfigs();
        for (Map.Entry entry : actionConfigs.entrySet()) {
            removeActionConfig(targetPackage, entry.getKey());
        }
    }

    /**
     * Copies the contents of the source map into the target map.
     * If an entry with the same name already exists in the target map, a {@link ConveyorException}
     * is thrown and no change is made to target
     *
     * @param source The source map.
     * @param target The target map.
     * @param type   The human-readable type name for the error message.
     * @param packageName Package name.
     * @param     The key type.
     * @param     The value type.
     * @throws ConveyorException if there is a duplicate.
     */
    protected  void copyMapValues(Map source, Map target, String type, String packageName) throws ConveyorException {
        for (K key : source.keySet()) {
            if (target.containsKey(key)) {
                throw new ConveyorException(type + " named '" + key
                        + " already exists in the '" + packageName
                        + "' package. Please select more unique name.");
            }
        }

        for (Map.Entry entry : source.entrySet()) {
            target.put(entry.getKey(), entry.getValue());
        }
    }

    /**
     * Removes items with the same key and value in the source from the target.
     *
     * @param source The source map.
     * @param target The target map.
     * @param     The key type.
     * @param     The value type.
     */
    protected  void removeMapValues(Map source, Map target) {
        if (source != null && target != null) {
            for (Map.Entry entry : source.entrySet()) {
                if (target.get(entry.getKey()) == entry.getValue()) {
                    source.remove(entry.getKey());
                }
            }
        }
    }


    private String getPackagePluginKey(PackageConfig packageConfig) {
        if (packageConfig instanceof PluginAwarePackageConfig) {
            Plugin plugin = ((PluginAwarePackageConfig) packageConfig).getPlugin();
            if (plugin != null) {
                return plugin.getKey();
            }
        }
        return null;
    }

    private String getActionPluginKey(ActionConfig actionConfig) {
        if (actionConfig instanceof PluginAwareActionConfig) {
            Plugin plugin = ((PluginAwareActionConfig) actionConfig).getPlugin();
            if (plugin != null) {
                return plugin.getKey();
            }
        }
        return null;
    }

    /**
     * Returns any existing {@link OverriddenPackageConfig} if already defined, or converts a package to an override.
     * it does not update the configuration with the OverriddenPackageConfig if a new one was created.
     *
     * @param name      The name of the package.
     * @param namespace The namespace it must have.
     * @return The package as an {@link OverriddenPackageConfig}.
     * @throws ConveyorException if there was a problem converting the existing package, or none exists.
     */
    private OverriddenPackageConfig asOverriddenPackageConfig(String name, String namespace) throws ConveyorException {
        OverriddenPackageConfig overriddenPackage;
        PackageConfig currentPackage = ConfigurationManager.getConfiguration().getPackageConfig(name);

        if (currentPackage == null) {
            throw new ConveyorException("Unable to locate the '" + name + "' package to override.");
        } else if (currentPackage instanceof OverriddenPackageConfig) {
            LOG.debug("Existing OverriddenPackageConfig for '" + name + "' package found.");
            overriddenPackage = (OverriddenPackageConfig) currentPackage;
        } else {
            if (!StringUtils.equals(currentPackage.getNamespace(), namespace)) {
                throw new ConveyorException("Requested an override of the '" + name + "' with a namespace of '"
                        + namespace + "', but the current package has a namespace of '"
                        + currentPackage.getNamespace() + "'.");
            }

            LOG.debug("Creating a new OverriddenPackageConfig for '" + name + "' package.");

            // Convert it.
            overriddenPackage = new OverriddenPackageConfig(currentPackage);
            copyPackageDetails(currentPackage, overriddenPackage);
        }
        return overriddenPackage;
    }

    public CreatePackageReceipt createPackage(PackageConfig packageConfig) throws ConveyorException {
        String name = packageConfig.getName();
        Configuration configuration = ConfigurationManager.getConfiguration();

        if (configuration.getPackageConfig(name) != null) {
            throw new ConveyorException("Unable to add a package named '" + name + "' because one already exists with the same name.");
        }

        configuration.addPackageConfig(name, packageConfig);

        return keepReceipt(new CreatePackageReceipt(name, this));
    }

    private  T keepReceipt(T receipt) {
        receipts.add(receipt);
        return receipt;
    }

    public Collection getOverriddenPackages() {
        return Collections.unmodifiableCollection(overriddenPackages.values());
    }

    public OverriddenPackageConfig getPackage(String packageName) {
        return overriddenPackages.get(packageName);
    }

    /**
     * Retrieves the 'real' {@link ActionConfig} for the current context. This is different
     * to what is available in via the {@link com.opensymphony.xwork.ActionContext#getContext()}
     * method because that come from the 'runtime' configuration which has been generated separately.
     *
     * @return The original {@link ActionConfig} for the current context.
     */
    public ActionConfig getCurrentActionConfig() {
        if (ActionContext.getContext() != null && ActionContext.getContext().getActionInvocation() != null &&
                ActionContext.getContext().getActionInvocation().getProxy() != null) {
            ActionConfig currentAction = ActionContext.getContext().getActionInvocation().getProxy().getConfig();
            try {
                ActionRequest request = ActionRequest.parse(ActionContext.getContext().getName());

                PackageConfig packageConfig = ConfigurationManager.getConfiguration().getPackageConfig(currentAction.getPackageName());
                if (packageConfig != null) {
                    return (ActionConfig) packageConfig.getActionConfigs().get(request.getActionName());
                }
            } catch (ConveyorException e) {
                //FIXME: Bad practice on catching exception without doing anything. Temporarily logging error message.
                LOG.debug(e.getMessage());
            }
        }

        return null;
    }

    /**
     * @return the {@link Plugin} instance for Conveyor.
     */
    public Plugin getConveyorPlugin() {
        return plugin;
    }

    /**
     * Returns a batch of {@link Receipt}s at once, which makes cleaning up the
     * XWork runtime configuration more efficient.
     *
     * @param receipts The collection of receipts to return.
     */
    public synchronized void returnReceipts(Iterable receipts) {
        try {
            returningReceipts++;
            for (Receipt receipt : receipts) {
                receipt.returnReceipt();
            }
        } finally {
            returningReceipts--;
            rebuildRuntimeConfiguration();
        }
    }

    public void rebuildRuntimeConfiguration() {
        if (returningReceipts == 0)
            ConfigurationManager.getConfiguration().rebuildRuntimeConfiguration();
    }

    public void destroy() throws Exception {
        for (Receipt receipt : receipts) {
            receipt.returnReceipt();
        }
        rebuildRuntimeConfiguration();
    }

    public void afterPropertiesSet() throws Exception {
        // Nothing to do.
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy