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