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