001/*
002 *  Copyright 2017 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.extraction.execution;
017
018import java.io.File;
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.Set;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang.StringUtils;
029import org.apache.excalibur.source.Source;
030import org.apache.excalibur.source.SourceResolver;
031import org.apache.excalibur.source.impl.FileSource;
032import org.quartz.JobKey;
033import org.quartz.SchedulerException;
034
035import org.ametys.cms.contenttype.ContentType;
036import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
037import org.ametys.cms.contenttype.ContentTypesHelper;
038import org.ametys.core.schedule.Runnable;
039import org.ametys.core.ui.Callable;
040import org.ametys.core.ui.StaticClientSideElement;
041import org.ametys.core.user.User;
042import org.ametys.core.user.UserIdentity;
043import org.ametys.core.user.UserManager;
044import org.ametys.core.util.JSONUtils;
045import org.ametys.plugins.core.schedule.Scheduler;
046import org.ametys.plugins.extraction.ExtractionConstants;
047import org.ametys.plugins.extraction.execution.Extraction.ClausesVariable;
048import org.ametys.plugins.extraction.execution.Extraction.ClausesVariableType;
049import org.ametys.plugins.extraction.execution.pipeline.PipelineManager;
050import org.ametys.runtime.i18n.I18nizableText;
051
052/**
053 * This client site element creates a button to execute an extraction
054 */
055public class ExecuteExtractionClientSideElement extends StaticClientSideElement
056{
057    private UserManager _userManager;
058    private ExtractionDefinitionReader _reader;
059    private SourceResolver _sourceResolver;
060    private JSONUtils _jsonUtils;
061    private Scheduler _scheduler;
062    private PipelineManager _pipelineManager;
063    private ContentTypeExtensionPoint _contentTypeExtensionPoint;
064    private ContentTypesHelper _contentTypesHelper;
065    
066    @Override
067    public void service(ServiceManager serviceManager) throws ServiceException
068    {
069        super.service(serviceManager);
070        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
071        _reader = (ExtractionDefinitionReader) serviceManager.lookup(ExtractionDefinitionReader.ROLE);
072        _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE);
073        _jsonUtils = (JSONUtils) serviceManager.lookup(JSONUtils.ROLE);
074        _scheduler = (Scheduler) serviceManager.lookup(Scheduler.ROLE);
075        _pipelineManager = (PipelineManager) serviceManager.lookup(PipelineManager.ROLE);
076        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
077        _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
078    }
079    
080    /**
081     * Retrieve needed extraction parameters.
082     * @param definitionFile The extraction definition file path
083     * @return a <code>Map</code> containing parameters infos used to configure form to fill the parameters
084     * @throws Exception if an error occurs
085     */
086    @Callable (rights = ExtractionConstants.EXECUTE_EXTRACTION_RIGHT_ID)
087    public Map<String, Object> getExecutionParameters(String definitionFile) throws Exception
088    {
089        Map<String, Object> executionParameters = new LinkedHashMap<>();
090
091        String definitionFilePath = ExtractionConstants.DEFINITIONS_DIR + definitionFile;
092        Source src = _sourceResolver.resolveURI(definitionFilePath);
093        File file = ((FileSource) src).getFile();
094        
095        if (!file.exists())
096        {
097            throw new IllegalArgumentException("The file " + definitionFilePath + " does not exist.");
098        }
099
100        executionParameters.put("pipeline", _getPipelineInputConfig(definitionFile));
101        
102        Extraction extraction = _reader.readExtractionDefinitionFile(file);
103        
104        List<ClausesVariable> clausesVariables = extraction.getClausesVariables();
105        List<String> optionalColumns = extraction.getDisplayOptionalColumnsNames();
106
107        if (!clausesVariables.isEmpty())
108        {
109            Map<String, Object> clausesVariablesFieldSet = new HashMap<>();
110            clausesVariablesFieldSet.put("role", "fieldset");
111            clausesVariablesFieldSet.put("label", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_CLAUSES_VARIABLES_FIELDSET_LABEL"));
112        
113            Map<String, Object> clausesVariablesFieldSetElements = new HashMap<>();
114            for (ClausesVariable clausesVariable : clausesVariables)
115            {
116                clausesVariablesFieldSetElements.put(clausesVariable.name(), _getClausesVariableInputConfig(clausesVariable));
117            }
118            clausesVariablesFieldSet.put("elements", clausesVariablesFieldSetElements);
119            
120            executionParameters.put("clausesVariables", clausesVariablesFieldSet);
121        }
122        
123        if (!optionalColumns.isEmpty())
124        {
125            Map<String, Object> optionalColumnsFieldSet = new HashMap<>();
126            optionalColumnsFieldSet.put("role", "fieldset");
127            optionalColumnsFieldSet.put("label", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_OPTIONAL_COLUMNS_FIELDSET_LABEL"));
128        
129            Map<String, Object> optionalColumnsFieldSetElements = new HashMap<>();
130            for (String optionalColumn : optionalColumns)
131            {
132                optionalColumnsFieldSetElements.put(optionalColumn, _getOptionalColumnsInputConfig(optionalColumn));
133            }
134            optionalColumnsFieldSet.put("elements", optionalColumnsFieldSetElements);
135            
136            executionParameters.put("optionalColumns", optionalColumnsFieldSet);
137        }
138            
139        executionParameters.put("recipient", _getRecipientInputConfig());
140        
141        return executionParameters;
142    }
143    
144    private Map<String, Object> _getPipelineInputConfig(String definitionFile)
145    {
146        Map<String, Object> inputConfig = new HashMap<>();
147        
148        inputConfig.put("label", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_PIPELINE_INPUT_LABEL"));
149        inputConfig.put("description", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_PIPELINE_INPUT_DESCRIPTION"));
150        inputConfig.put("type", "string");
151        
152        inputConfig.put("default-value", _pipelineManager.getDefaultPipeline());
153        inputConfig.put("validation", _getMandatoryValidation());
154        
155        inputConfig.put("widget", "edition.select-pipeline");
156        
157        Map<String, Object> widgetParams = new HashMap<>();
158        widgetParams.put("extraction", definitionFile);
159        inputConfig.put("widget-params", widgetParams);
160        
161        return inputConfig;
162    }
163    
164    private Map<String, Object> _getClausesVariableInputConfig(ClausesVariable clausesVariable)
165    {
166        Map<String, Object> inputConfig = new HashMap<>();
167        
168        inputConfig.put("label", clausesVariable.name());
169        
170        if (ClausesVariableType.SOLR_REQUEST.equals(clausesVariable.type()))
171        {
172            inputConfig.put("description", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_CLAUSES_VARIABLE_SOLR_REQUEST_INPUT_DESCRIPTION"));
173
174            inputConfig.put("type", "string");
175            inputConfig.put("widget", "edition.solr-code");
176            
177            Map<String, Object> widgetParams = new HashMap<>();
178            
179            Set<String> commonAncestors = _contentTypesHelper.getCommonAncestors(clausesVariable.contentTypeIds());
180            widgetParams.put("ctypes", commonAncestors);            
181            widgetParams.put("singleLine", true);
182            widgetParams.put("height", 66);
183            widgetParams.put("mode", "text/x-solr-ametys");
184            
185            inputConfig.put("widget-params", widgetParams);
186        }
187        else
188        {
189            inputConfig.put("description", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_CLAUSES_VARIABLE_SELECT_CONTENTS_INPUT_DESCRIPTION"));
190    
191            inputConfig.put("type", "content");
192            inputConfig.put("multiple", true);
193            
194            String widget = "edition.select-content";
195            Map<String, Object> widgetParams = new HashMap<>();
196            
197            Optional<String> contentTypeId = clausesVariable.contentTypeIds().stream().findFirst();
198            if (contentTypeId.isPresent())
199            {
200                ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId.get());
201                if (contentType.isReferenceTable())
202                {
203                    widget = "edition.select-referencetable-content";
204                }
205                
206                widgetParams.put("contentType", contentTypeId.get());
207            }
208            
209            clausesVariable.searchModelId()
210                           .ifPresent(searchModelId -> widgetParams.put("modelId", searchModelId));
211            
212            clausesVariable.solrRequest()
213                           .ifPresent(solrRequest -> widgetParams.put("solrRequest", solrRequest));
214
215            inputConfig.put("widget", widget);
216            inputConfig.put("widget-params", widgetParams);
217        }
218        
219        inputConfig.put("validation", _getMandatoryValidation());
220        
221        return inputConfig;
222    }
223    
224    private Map<String, Object> _getOptionalColumnsInputConfig(String optionalColumn)
225    {
226        Map<String, Object> inputConfig = new HashMap<>();
227        
228        inputConfig.put("label", optionalColumn);
229        inputConfig.put("description", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_OPTIONAL_COLUMNS_INPUTS_DESCRIPTION"));
230        
231        inputConfig.put("type", "boolean");
232        inputConfig.put("widget", "edition.checkbox");
233        
234        inputConfig.put("validation", _getMandatoryValidation());
235        
236        return inputConfig;
237    }
238    
239    private Map<String, Object> _getRecipientInputConfig()
240    {
241        Map<String, Object> inputConfig = new HashMap<>();
242        
243        // Get current user email
244        String currentUserEmail = null;
245        UserIdentity currentUser = _currentUserProvider.getUser();
246        
247        String login = currentUser.getLogin();
248        if (StringUtils.isNotBlank(login))
249        {
250            String userPopulationId = currentUser.getPopulationId();
251            User user = _userManager.getUser(userPopulationId, login);
252            currentUserEmail = user.getEmail();
253        }
254        
255        inputConfig.put("label", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_RECIPIENT_INPUT_LABEL"));
256        inputConfig.put("description", new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_RECIPIENT_INPUT_DESCRIPTION"));
257        inputConfig.put("type", "string");
258        inputConfig.put("default-value", currentUserEmail);
259        
260        return inputConfig;
261    }
262    
263    private Map<String, Object> _getMandatoryValidation()
264    {
265        Map<String, Object> mandatoryValidation = new HashMap<>();
266        mandatoryValidation.put("mandatory", true);
267        return mandatoryValidation;
268    }
269    
270    /**
271     * Execute the extraction
272     * @param definitionFilePath The extraction definition file path
273     * @param variables clauses variables and optional columns
274     * @param recipient An email will be sent at this address when the extraction is complete
275     * @param pipelineId The id of the extraction pipeline
276     * @return a Map with error if one occurs
277     * @throws Exception if an error occurs
278     */
279    @Callable (rights = ExtractionConstants.EXECUTE_EXTRACTION_RIGHT_ID)
280    public Map<String, Object> executeExtraction(String definitionFilePath, Map<String, Object> variables, String recipient, String pipelineId) throws Exception
281    {
282        Map<String, Object> result = new HashMap<>();
283        
284        try
285        {
286            String variablesAsString = _jsonUtils.convertObjectToJson(variables);
287            Runnable executeExtractionRunnable = new ExecuteExtractionRunnable(definitionFilePath, variablesAsString, recipient, pipelineId);
288            JobKey jobKey = new JobKey(executeExtractionRunnable.getId(), Scheduler.JOB_GROUP);
289            if (_scheduler.getScheduler().checkExists(jobKey))
290            {
291                _scheduler.getScheduler().deleteJob(jobKey);
292            }
293            _scheduler.scheduleJob(executeExtractionRunnable);
294            getLogger().info("Scheduled extraction execution of " + definitionFilePath);
295        }
296        catch (SchedulerException e)
297        {
298            if (getLogger().isErrorEnabled())
299            {
300                getLogger().error("An error occured when trying to schedule the extraction execution of " + definitionFilePath, e);
301            }
302            result.put("error", "scheduler-error");
303            return result;
304        }
305        
306        result.put("definitionFilePath", definitionFilePath);
307        return result;
308    }
309}