001/*
002 *  Copyright 2021 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.workflow.definition;
017
018import java.io.File;
019import java.util.Optional;
020
021import org.apache.avalon.framework.configuration.Configuration;
022import org.apache.avalon.framework.configuration.ConfigurationException;
023import org.apache.avalon.framework.configuration.DefaultConfiguration;
024import org.apache.avalon.framework.configuration.MutableConfiguration;
025import org.apache.avalon.framework.context.ContextException;
026import org.apache.cocoon.Constants;
027import org.apache.cocoon.environment.Context;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
031
032/**
033 * The {@link WorkflowDefinition} extension point to list all available workflows.
034 */
035public class WorkflowDefinitionExtensionPoint extends AbstractThreadSafeComponentExtensionPoint<WorkflowDefinition>
036{
037    /** The Avalon role */
038    public static final String ROLE = WorkflowDefinitionExtensionPoint.class.getName();
039    
040    /** The Cocoon context */
041    protected Context _cocoonContext;
042    
043    @Override
044    public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException
045    {
046        super.contextualize(context);
047        _cocoonContext = (Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
048    }
049    
050    @Override
051    public void initializeExtensions() throws Exception
052    {
053        /* Add custom workflow found in WEB-INF/param/workflows and ignore those which are already defined in the kernel */
054        
055        // List workflow files
056        File[] workflowFiles = _getParamWorkflowDir().listFiles((file, name) -> name.endsWith(".xml"));
057
058        if (workflowFiles != null)
059        {
060            for (File workflowFile : workflowFiles)
061            {
062                String workflowId = _getWorkflowIdFromFilename(workflowFile.getName());
063                
064                // If the workflow definition is not already declared, add it to known extensions
065                if (!hasExtension(workflowId))
066                {
067                    DefaultConfiguration extensionConf = new DefaultConfiguration("extension");
068                    extensionConf.setAttribute("class", WorkflowDefinition.class.getName());
069                    
070                    // Set null to the configuration because the configuration is get into the method
071                    addExtension(workflowId, "unknown", "unknown", extensionConf);
072                }
073            }
074        }
075        
076        super.initializeExtensions();
077    }
078    
079    @Override
080    public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException
081    {
082        DefaultConfiguration extensionConf = new DefaultConfiguration(configuration, true);
083        
084        MutableConfiguration workflowFileConf = extensionConf.getMutableChild("file", true);
085        
086        // Search in WEB-INF/param/workflows/[workflowId].xml
087        String filepath = _getFilePathFromParam(id)
088                // Then in the path defined by the "file" child of the configuration if defined
089                .or(() -> _getFilePathFromConf(workflowFileConf))
090                // Then in [plugin]/workflows/[workflowId].xml
091                .orElseGet(() -> _getFilePathFromPlugin(pluginName, id));
092        
093        workflowFileConf.setValue(filepath);
094        
095        super.addExtension(id, pluginName, featureName, extensionConf);
096    }
097    
098    private Optional<String> _getFilePathFromParam(String workflowId)
099    {
100        return Optional.of(workflowId)
101                .map(id -> id + ".xml")
102                .map(filename -> new File(_getParamWorkflowDir(), filename))
103                .filter(File::exists)
104                .filter(File::isFile)
105                // Transform it to URI with context://
106                .map(file -> "context://WEB-INF/param/workflows/" + file.getName());
107    }
108    
109    private Optional<String> _getFilePathFromConf(Configuration workflowFileConf)
110    {
111        return Optional.of(workflowFileConf)
112                .map(conf -> workflowFileConf.getValue(null))
113                .filter(StringUtils::isNotBlank);
114    }
115    
116    private String _getFilePathFromPlugin(String pluginName, String workflowId)
117    {
118        return "plugin:" + pluginName + "://workflows/" + workflowId + ".xml";
119    }
120    
121    /**
122     * Get the param workflows directory.
123     * @return the workflows directory in WEB-INF/param.
124     */
125    protected File _getParamWorkflowDir()
126    {
127        return new File(_cocoonContext.getRealPath("/WEB-INF/param/workflows"));
128    }
129
130    /**
131     * Get the workflow ID from the workflow filename.
132     * @param filename The workflow filename
133     * @return the workflow ID
134     */
135    protected String _getWorkflowIdFromFilename(String filename)
136    {
137        return filename.substring(0, filename.lastIndexOf('.'));
138    }
139}