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