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.repository.Content;
034import org.ametys.cms.repository.WorkflowAwareContent;
035import org.ametys.cms.search.SearchField;
036import org.ametys.cms.search.model.SystemProperty;
037import org.ametys.cms.search.query.OrQuery;
038import org.ametys.cms.search.query.Query;
039import org.ametys.cms.search.query.Query.Operator;
040import org.ametys.cms.search.query.WorkflowStepQuery;
041import org.ametys.cms.search.solr.field.WorkflowStepSearchField;
042import org.ametys.cms.workflow.DefaultWorkflowStepEnumerator;
043import org.ametys.core.model.type.ModelItemTypeHelper;
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 a content.
059 */
060public class WorkflowStepSystemProperty extends AbstractSystemProperty<Long, Content>
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(Content content)
153    {
154        if (content instanceof WorkflowAwareContent waContent)
155        {
156            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
157            
158            long workflowId = waContent.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(Content content, DataContext context)
171    {
172        Long value = (Long) getValue(content);
173        if (value != null)
174        {
175            Map<String, Object> workflowInfos = new LinkedHashMap<>();
176            
177            if (content instanceof WorkflowAwareContent)
178            {
179                WorkflowAwareContent waContent = (WorkflowAwareContent) content;
180                int currentStepId = Math.toIntExact(value);
181                
182                StepDescriptor stepDescriptor = _workflowHelper.getStepDescriptor(waContent, currentStepId);
183                
184                if (stepDescriptor != null)
185                {
186                    return _workflowHelper.workflowStep2JSON(stepDescriptor);
187                }
188            }
189            
190            return workflowInfos;
191        }
192        return value;
193    }
194    
195    public void valueToSAX(ContentHandler contentHandler, Content content, DataContext context) throws SAXException
196    {
197        Long value = (Long) getValue(content);
198        if (value != null && content instanceof WorkflowAwareContent waContent)
199        {
200            int currentStepId = Math.toIntExact(value);
201            StepDescriptor stepDescriptor = _getStepDescriptor(waContent, currentStepId);
202            
203            if (stepDescriptor != null)
204            {
205                I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName());
206                
207                AttributesImpl attr = ModelItemTypeHelper.getXMLAttributesFromDataContext(context);
208                attr.addCDATAAttribute("id", String.valueOf(currentStepId));
209                XMLUtils.startElement(contentHandler, getName(), attr);
210                workflowStepName.toSAX(contentHandler);
211                XMLUtils.endElement(contentHandler, getName());
212            }
213        }
214    }
215    
216    private StepDescriptor _getStepDescriptor(WorkflowAwareContent content, int stepId)
217    {
218        long workflowId = content.getWorkflowId();
219        
220        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content);
221        
222        String workflowName = workflow.getWorkflowName(workflowId);
223        WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName);
224        
225        if (workflowDescriptor != null)
226        {
227            StepDescriptor stepDescriptor = workflowDescriptor.getStep(stepId);
228            if (stepDescriptor != null)
229            {
230                return stepDescriptor;
231            }
232            else if (_logger.isWarnEnabled())
233            {
234                _logger.warn("Unknown step id '" + stepId + "' for workflow for name : " + workflowName);
235            }
236        }
237        else if (_logger.isWarnEnabled())
238        {
239            _logger.warn("Unknown workflow for name : " + workflowName);
240        }
241        
242        return null;
243    }
244    
245    public Enumerator<String> getCriterionEnumerator(Configuration configuration, ThreadSafeComponentManager<Enumerator> enumeratorManager) throws ConfigurationException
246    {
247        DefaultConfiguration conf = new DefaultConfiguration("criteria");
248        
249        String workflowName = configuration.getChild("workflow").getAttribute("name", "content");
250        
251        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
252        
253        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
254        customEnumerator.setAttribute("class", DefaultWorkflowStepEnumerator.class.getName());
255        enumConf.addChild(customEnumerator);
256        
257        DefaultConfiguration wfNameConf = new DefaultConfiguration("workflow-name");
258        wfNameConf.setValue(workflowName);
259        customEnumerator.addChild(wfNameConf);
260        
261        DefaultConfiguration excludeConf = new DefaultConfiguration("exclude-workflow-steps");
262        
263        // Exclude workflow steps greater than 9000
264        List<StepDescriptor> steps = _workflowHelper.getWorkflowDescriptor(workflowName).getSteps();
265        for (StepDescriptor stepDescriptor : steps)
266        {
267            if (stepDescriptor.getId() >= 9000)
268            {
269                DefaultConfiguration stepId = new DefaultConfiguration("id");
270                stepId.setValue(stepDescriptor.getId());
271                excludeConf.addChild(stepId);
272            }
273        }
274        
275        customEnumerator.addChild(excludeConf);
276        
277        conf.addChild(enumConf);
278        
279        String role = "enumerator";
280        enumeratorManager.addComponent(getPluginName(), null, role, DefaultWorkflowStepEnumerator.class, conf);
281        
282        try
283        {
284            enumeratorManager.initialize();
285            return enumeratorManager.lookup(role);
286        }
287        catch (Exception e)
288        {
289            throw new ConfigurationException("Unable to initialize the workflow step enumerator for system property '" + getName() + "'.", conf, e);
290        }
291    }
292
293    @Override
294    protected String _getTypeId()
295    {
296        return ModelItemTypeConstants.LONG_TYPE_ID;
297    }
298}