001/*
002 *  Copyright 2015 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.cms.search.systemprop;
017
018import java.util.ArrayList;
019import java.util.LinkedHashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.configuration.ConfigurationException;
025import org.apache.avalon.framework.configuration.DefaultConfiguration;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.xml.AttributesImpl;
029import org.apache.cocoon.xml.XMLUtils;
030import org.xml.sax.ContentHandler;
031import org.xml.sax.SAXException;
032
033import org.ametys.cms.data.ametysobject.ModelAwareDataAwareAmetysObject;
034import org.ametys.cms.search.SearchField;
035import org.ametys.cms.search.model.SystemProperty;
036import org.ametys.cms.search.query.OrQuery;
037import org.ametys.cms.search.query.Query;
038import org.ametys.cms.search.query.Query.Operator;
039import org.ametys.cms.search.query.WorkflowStepQuery;
040import org.ametys.cms.search.solr.field.WorkflowStepSearchField;
041import org.ametys.cms.workflow.DefaultWorkflowStepEnumerator;
042import org.ametys.core.model.type.ModelItemTypeHelper;
043import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObject;
044import org.ametys.plugins.workflow.support.WorkflowHelper;
045import org.ametys.plugins.workflow.support.WorkflowProvider;
046import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.model.Enumerator;
049import org.ametys.runtime.model.type.DataContext;
050import org.ametys.runtime.model.type.ModelItemTypeConstants;
051import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
052
053import com.opensymphony.workflow.loader.StepDescriptor;
054import com.opensymphony.workflow.loader.WorkflowDescriptor;
055import com.opensymphony.workflow.spi.Step;
056
057/**
058 * {@link SystemProperty} which represents the current workflow step ID of an ametys object.
059 */
060public class WorkflowStepSystemProperty extends AbstractSystemProperty<Long, ModelAwareDataAwareAmetysObject>
061{
062    /** The workflow provider */
063    protected WorkflowProvider _workflowProvider;
064    /** The workflow helper */
065    protected WorkflowHelper _workflowHelper;
066    
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        super.service(manager);
071        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
072        _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE);
073    }
074    
075    @Override
076    public boolean isMultiple()
077    {
078        return false;
079    }
080    
081    @Override
082    public boolean isSortable()
083    {
084        return true;
085    }
086    
087    @SuppressWarnings("unchecked")
088    @Override
089    public Query getQuery(Object value, Operator operator, String language, Map<String, Object> contextualParameters)
090    {
091        if (value == null)
092        {
093            return new WorkflowStepQuery(Operator.EXISTS);
094        }
095        
096        List<Object> values;
097        if (value instanceof List)
098        {
099            values = (List<Object>) value;
100        }
101        else
102        {
103            values = List.of(value);
104        }
105        
106        List<Query> queries = new ArrayList<>();
107        for (Object val : values)
108        {
109            if (!"".equals(val)) // empty value when selecting "All" entry in the widget
110            {
111                long stepId = parseLong(val);
112                if (stepId != 0)
113                {
114                    queries.add(new WorkflowStepQuery(operator, (int) stepId));
115                }
116            }
117        }
118        
119        switch (queries.size())
120        {
121            case 0: return null;
122            case 1: return queries.get(0);
123            default: return new OrQuery(queries);
124        }
125    }
126    
127    @Override
128    public SearchField getSearchField()
129    {
130        return new WorkflowStepSearchField();
131    }
132    
133    @Override
134    public String getRenderer()
135    {
136        return "Ametys.plugins.cms.search.SearchGridHelper.renderWorkflowStep";
137    }
138    
139    @Override
140    public String getConverter()
141    {
142        return "Ametys.plugins.cms.search.SearchGridHelper.convertWorkflowStep";
143    }
144    
145    @Override
146    public Integer getColumnWidth()
147    {
148        return 60;
149    }
150    
151    @Override
152    public Object getValue(ModelAwareDataAwareAmetysObject ametysObject)
153    {
154        if (ametysObject instanceof WorkflowAwareAmetysObject waAmetysObject)
155        {
156            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waAmetysObject);
157            
158            long workflowId = waAmetysObject.getWorkflowId();
159            List<Step> currentSteps = workflow.getCurrentSteps(workflowId);
160            
161            if (!currentSteps.isEmpty())
162            {
163                return Long.valueOf(currentSteps.iterator().next().getStepId());
164            }
165        }
166        
167        return null;
168    }
169    
170    public Object valueToJSON(ModelAwareDataAwareAmetysObject ametysObject, DataContext context)
171    {
172        Long value = (Long) getValue(ametysObject);
173        if (value != null)
174        {
175            Map<String, Object> workflowInfos = new LinkedHashMap<>();
176            
177            if (ametysObject instanceof WorkflowAwareAmetysObject waAmetysObject)
178            {
179                int currentStepId = Math.toIntExact(value);
180                
181                StepDescriptor stepDescriptor = _workflowHelper.getStepDescriptor(waAmetysObject, currentStepId);
182                
183                if (stepDescriptor != null)
184                {
185                    return _workflowHelper.workflowStep2JSON(stepDescriptor);
186                }
187            }
188            
189            return workflowInfos;
190        }
191        return value;
192    }
193    
194    public void valueToSAX(ContentHandler contentHandler, ModelAwareDataAwareAmetysObject ametysObject, DataContext context) throws SAXException
195    {
196        Long value = (Long) getValue(ametysObject);
197        if (value != null && ametysObject instanceof WorkflowAwareAmetysObject waAmetysObject)
198        {
199            int currentStepId = Math.toIntExact(value);
200            StepDescriptor stepDescriptor = _getStepDescriptor(waAmetysObject, currentStepId);
201            
202            if (stepDescriptor != null)
203            {
204                I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName());
205                
206                AttributesImpl attr = ModelItemTypeHelper.getXMLAttributesFromDataContext(context);
207                attr.addCDATAAttribute("id", String.valueOf(currentStepId));
208                XMLUtils.startElement(contentHandler, getName(), attr);
209                workflowStepName.toSAX(contentHandler);
210                XMLUtils.endElement(contentHandler, getName());
211            }
212        }
213    }
214    
215    private StepDescriptor _getStepDescriptor(WorkflowAwareAmetysObject ametysObject, int stepId)
216    {
217        long workflowId = ametysObject.getWorkflowId();
218        
219        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(ametysObject);
220        
221        String workflowName = workflow.getWorkflowName(workflowId);
222        WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName);
223        
224        if (workflowDescriptor != null)
225        {
226            StepDescriptor stepDescriptor = workflowDescriptor.getStep(stepId);
227            if (stepDescriptor != null)
228            {
229                return stepDescriptor;
230            }
231            else if (_logger.isWarnEnabled())
232            {
233                _logger.warn("Unknown step id '" + stepId + "' for workflow for name : " + workflowName);
234            }
235        }
236        else if (_logger.isWarnEnabled())
237        {
238            _logger.warn("Unknown workflow for name : " + workflowName);
239        }
240        
241        return null;
242    }
243    
244    public Enumerator<String> getCriterionEnumerator(Configuration configuration, ThreadSafeComponentManager<Enumerator> enumeratorManager) throws ConfigurationException
245    {
246        DefaultConfiguration conf = new DefaultConfiguration("criteria");
247        
248        String workflowName = configuration.getChild("workflow").getAttribute("name", "content");
249        
250        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
251        
252        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
253        customEnumerator.setAttribute("class", DefaultWorkflowStepEnumerator.class.getName());
254        enumConf.addChild(customEnumerator);
255        
256        DefaultConfiguration wfNameConf = new DefaultConfiguration("workflow-name");
257        wfNameConf.setValue(workflowName);
258        customEnumerator.addChild(wfNameConf);
259        
260        DefaultConfiguration excludeConf = new DefaultConfiguration("exclude-workflow-steps");
261        
262        // Exclude workflow steps greater than 9000
263        List<StepDescriptor> steps = _workflowHelper.getWorkflowDescriptor(workflowName).getSteps();
264        for (StepDescriptor stepDescriptor : steps)
265        {
266            if (stepDescriptor.getId() >= 9000)
267            {
268                DefaultConfiguration stepId = new DefaultConfiguration("id");
269                stepId.setValue(stepDescriptor.getId());
270                excludeConf.addChild(stepId);
271            }
272        }
273        
274        customEnumerator.addChild(excludeConf);
275        
276        conf.addChild(enumConf);
277        
278        String role = "enumerator";
279        enumeratorManager.addComponent(getPluginName(), null, role, DefaultWorkflowStepEnumerator.class, conf);
280        
281        try
282        {
283            enumeratorManager.initialize();
284            return enumeratorManager.lookup(role);
285        }
286        catch (Exception e)
287        {
288            throw new ConfigurationException("Unable to initialize the workflow step enumerator for system property '" + getName() + "'.", conf, e);
289        }
290    }
291
292    @Override
293    protected String _getTypeId()
294    {
295        return ModelItemTypeConstants.LONG_TYPE_ID;
296    }
297}