org.obiba.onyx.engine.ActionDefinitionConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.obiba.onyx.engine.ActionDefinitionConfiguration.java

Source

/*******************************************************************************
 * Copyright 2008(c) The OBiBa Consortium. All rights reserved.
 * 
 * This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package org.obiba.onyx.engine;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;

/**
 * Holds all {@link ActionDefinition} instances that drive the pop-up displays when an action is performed on a stage.
 * <p>
 * This implementations relies on the {@link ActionDefinitionReader} class to load instances from XML files. The files
 * are loaded in the following order (to allow the later to override the first):
 * <ul>
 * <li>classpath*:/META-INF/action-definitions.xml</li>
 * <li>onyxConfigPath/action-definitions.xml</li>
 * <li>onyxConfigPath/*\/action-definitions.xml</li>
 * </ul>
 */
public class ActionDefinitionConfiguration implements ResourceLoaderAware, InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(ActionDefinitionConfiguration.class);

    private static final String ACTION_DEFINITION_FILENAME = "action-definitions.xml";

    private static final char SEPARATOR = '.';

    private static final String ACTION_PREFIX = "action";

    private String onyxConfigPath;

    private ResourceLoader resourceLoader;

    private Map<String, ActionDefinition> actionDefinitionCache = Collections
            .synchronizedMap(new HashMap<String, ActionDefinition>());

    public ActionDefinition getActionDefinition(ActionType type, String stateName, String module, String stage) {
        String[] codes = calculateCodes(type, stateName, module, stage);
        for (int i = 0; i < codes.length; i++) {
            String code = codes[i].toLowerCase();
            log.debug("Looking up ActionDefinition for code {}", code);
            ActionDefinition actionDefinition = this.actionDefinitionCache.get(code);
            if (actionDefinition != null) {
                log.debug("Found ActionDefinition with code {}", code);

                // treat the case of the top-level "template" ActionDefinition that is associated to all types.
                String newCode = buildCode(type, stateName, module, stage);
                if (code.equals(newCode) == false) {
                    // copy the default ActionDefinition
                    actionDefinition = new ActionDefinition(actionDefinition);
                    // set the copy's type to the required type.
                    actionDefinition.setType(type);
                    // set the copy's code to the most specific.
                    actionDefinition.setCode(buildCode(type, stateName, module, stage));
                    actionDefinitionCache.put(actionDefinition.getCode().toLowerCase(), actionDefinition);
                }

                return actionDefinition;
            }
        }
        return null;
    }

    public ActionDefinition getActionDefinition(ActionType actionType, String code) {
        ActionDefinitionCode codeObj = new ActionDefinitionCode(actionType, code);
        return getActionDefinition(codeObj.getType(), codeObj.getState(), codeObj.getModule(), codeObj.getStage());
    }

    public void afterPropertiesSet() throws Exception {
        findDefinitions();
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    public void setOnyxConfigPath(String onyxConfigPath) {
        this.onyxConfigPath = onyxConfigPath;
    }

    /**
     * Returns an array of hierarchical codes that are used to find an associated {@code ActionDefinition} instance. This
     * method will build an array of codes with the first element being the most specific:
     * <ul>
     * <li>ACTION_PREFIX.type.stateName.module.stage</li>
     * <li>ACTION_PREFIX.type.stateName.module</li>
     * <li>ACTION_PREFIX.type.stateName</li>
     * <li>ACTION_PREFIX.type</li>
     * <li>ACTION_PREFIX
     * <li>
     * </ul>
     * @param type the {@link ActionType} of the {@code ActionDefinition}
     * @param stateName the name of the state on which the action is performed
     * @param module the module that contributed the stage
     * @param stage the stage on which the action is performed
     * @return an array of codes
     */
    static public String[] calculateCodes(ActionType type, String stateName, String module, String stage) {
        ArrayList<String> codes = new ArrayList<String>();
        StringBuilder sb = new StringBuilder(ACTION_PREFIX);
        codes.add(sb.toString());
        sb.append(SEPARATOR).append(type);
        codes.add(sb.toString());
        if (stateName != null) {
            sb.append(SEPARATOR).append(stateName);
            codes.add(sb.toString());
        }
        if (module != null) {
            sb.append(SEPARATOR).append(module);
            codes.add(sb.toString());
        }
        if (stage != null) {
            sb.append(SEPARATOR).append(stage);
            codes.add(sb.toString());
        }
        Collections.reverse(codes);
        return codes.toArray(new String[codes.size()]);
    }

    /**
     * Extracts an array of codes from the specified {@code ActionDefinition} that respects the algorithm defined in
     * {@link ActionDefinitionConfiguration#calculateCodes(ActionType, String, String, String)}. If the {@code
     * ActionDefinition}'s code is
     * 
     * <pre>
     * action.execute.skipped
     * </pre>
     * 
     * the array of codes returned will contain:
     * <ul>
     * <li>action.execute.skipped</li>
     * <li>action.execute</li>
     * <li>action</li>
     * </ul>
     * @param definition the instance from which to extract the codes.
     * @param suffix a suffix to append to all generated codes
     * @return an array of codes extracted from the definition.
     */
    static public String[] calculateCodes(ActionDefinition definition, String suffix) {
        ActionDefinitionCode code = new ActionDefinitionCode(definition.getType(), definition.getCode());

        String[] codes = ActionDefinitionConfiguration.calculateCodes(code.getType(), code.getState(),
                code.getModule(), code.getStage());
        if (suffix != null) {
            for (int i = 0; i < codes.length; i++) {
                codes[i] = codes[i] += suffix;
            }
        }

        return codes;
    }

    static private String buildCode(ActionType type, String stateName, String module, String stage) {
        StringBuilder sb = new StringBuilder(ACTION_PREFIX).append(SEPARATOR).append(type);
        if (stateName != null) {
            sb.append(SEPARATOR).append(stateName);
        }
        if (module != null) {
            sb.append(SEPARATOR).append(module);
        }
        if (stage != null) {
            sb.append(SEPARATOR).append(stage);
        }
        return sb.toString();
    }

    /**
     * Finds and loads action-definition.xml to build the cache of {@code ActionDefinition} instances.
     * @throws IOException
     */
    protected void findDefinitions() throws IOException {
        ResourcePatternResolver resolver = (ResourcePatternResolver) this.resourceLoader;

        // Find definitions in onyx jar files (including module jar files)
        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "META-INF/"
                + ACTION_DEFINITION_FILENAME;
        Resource[] resources = resolver.getResources(pattern);
        loadDefinitions(resources);

        Resource configPath = resolver.getResource(onyxConfigPath);
        if (configPath != null && configPath.exists()) {
            // Find definitions in the configuration directory
            resources = resolver.getResources(onyxConfigPath + "/" + ACTION_DEFINITION_FILENAME);
            loadDefinitions(resources);

            // Find definitions in the module configuration directory
            resources = resolver.getResources(onyxConfigPath + "/*/" + ACTION_DEFINITION_FILENAME);
            loadDefinitions(resources);
        }
    }

    /**
     * Delegates to an {@link ActionDefinitionReader} instance to deserialize a list of {@code ActionDefinition} instances
     * and caches the result.
     * @param resources
     * @throws IOException
     */
    protected void loadDefinitions(Resource[] resources) throws IOException {
        if (resources != null && resources.length > 0) {
            ActionDefinitionReader reader = new ActionDefinitionReader();
            reader.setResources(resources);
            List<ActionDefinition> definitions = reader.read();
            for (ActionDefinition actionDefinition : definitions) {
                this.actionDefinitionCache.put(actionDefinition.getCode().toLowerCase(), actionDefinition);
            }
        }
    }

    //
    // Inner Classes
    //

    private static class ActionDefinitionCode {
        private ActionType type;

        private String state;

        private String module;

        private String stage;

        public ActionDefinitionCode(ActionType actionType, String code) {
            String[] codeElements = code.split("\\.");

            type = (codeElements.length >= 2) ? ActionType.valueOf(codeElements[1]) : null;
            state = (codeElements.length >= 3) ? codeElements[2] : null;
            module = (codeElements.length >= 4) ? codeElements[3] : null;
            stage = (codeElements.length >= 5) ? codeElements[4] : null;

            // If no type was present in the code, use the specified ActionType.
            if (type == null) {
                type = actionType;
            }
        }

        public ActionType getType() {
            return type;
        }

        public String getState() {
            return state;
        }

        public String getModule() {
            return module;
        }

        public String getStage() {
            return stage;
        }
    }
}