001/*
002 *  Copyright 2016 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.content.indexing.solr;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Date;
021import java.util.List;
022
023import org.apache.avalon.framework.activity.Initializable;
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.solr.client.solrj.SolrClient;
029import org.apache.solr.client.solrj.SolrServerException;
030import org.apache.solr.client.solrj.response.UpdateResponse;
031import org.apache.solr.client.solrj.util.ClientUtils;
032import org.apache.solr.common.SolrInputDocument;
033
034import org.ametys.cms.search.solr.SolrClientProvider;
035import org.ametys.plugins.repository.AmetysObject;
036import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObject;
037import org.ametys.plugins.workflow.support.WorkflowProvider;
038import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
039import org.ametys.runtime.config.Config;
040import org.ametys.runtime.plugin.component.AbstractLogEnabled;
041
042import com.opensymphony.workflow.spi.Step;
043
044/**
045 * Component indexing the whole workflow of {@link AmetysObject}s.
046 */
047public class SolrWorkflowIndexer extends AbstractLogEnabled implements Component, Serviceable, SolrFieldNames, Initializable
048{
049    /** The component role. */
050    public static final String ROLE = SolrWorkflowIndexer.class.getName();
051    
052    /** The Solr client provider */
053    protected SolrClientProvider _solrClientProvider;
054    
055    /** The workflow provider. */
056    protected WorkflowProvider _workflowProvider;
057
058    private boolean _enabled;
059
060    @Override
061    public void service(ServiceManager serviceManager) throws ServiceException
062    {
063        _solrClientProvider = (SolrClientProvider) serviceManager.lookup(SolrClientProvider.ROLE);
064        _workflowProvider = (WorkflowProvider) serviceManager.lookup(WorkflowProvider.ROLE);
065    }
066    
067    public void initialize() throws Exception
068    {
069        _enabled = Config.getInstance().getValue("cms.solr.index.workflow");
070    }
071    
072    /**
073     * Index the workflow history of an Ametys object.
074     * @param object The ametys object.
075     * @param workspaceName The workspace where to work in
076     * @throws SolrServerException If a solr error occurs.
077     * @throws IOException If an I/O error occurs while indexing.
078     */
079    public void indexAmetysObjectWorkflow(WorkflowAwareAmetysObject object, String workspaceName) throws SolrServerException, IOException
080    {
081        if (_enabled)
082        {
083            SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName, true);
084            indexAmetysObjectWorkflow(object, workspaceName, solrClient);
085        }
086    }
087    
088    /**
089     * Index the workflow history of an Ametys object.
090     * @param object The ametys object.
091     * @param workspaceName The workspace where to work in
092     * @param solrClient The solr client to use
093     * @throws SolrServerException If a solr error occurs.
094     * @throws IOException If an I/O error occurs while indexing.
095     */
096    public void indexAmetysObjectWorkflow(WorkflowAwareAmetysObject object, String workspaceName, SolrClient solrClient) throws SolrServerException, IOException
097    {
098        if (_enabled)
099        {
100            long time_0 = System.currentTimeMillis();
101            getLogger().debug("Indexing the workflow of {}", object);
102            
103            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(object);
104            
105            long workflowId = object.getWorkflowId();
106            List<Step> currentSteps = workflow.getCurrentSteps(workflowId);
107            List<Step> historySteps = workflow.getHistorySteps(workflowId);
108            
109            String objectId = object.getId();
110            
111            List<SolrInputDocument> documents = new ArrayList<>();
112            
113            List<String> historyStepIds = indexSteps(historySteps, objectId, documents);
114            List<String> currentStepIds = indexSteps(currentSteps, objectId, documents);
115            
116            indexWorkflowEntry(workflow, workflowId, objectId, historyStepIds, currentStepIds, documents);
117            
118            String collection = _solrClientProvider.getCollectionName(workspaceName);
119            UpdateResponse solrResponse = solrClient.add(collection, documents);
120            int status = solrResponse.getStatus();
121            
122            if (status != 0)
123            {
124                throw new IOException("Workflow history indexation error (status code=" + status + ")");
125            }
126            
127            getLogger().debug("Successfully indexed workflow of {} in {} ms", object, System.currentTimeMillis() - time_0);
128        }
129    }
130    
131    private void indexWorkflowEntry(AmetysObjectWorkflow workflow, long workflowId, String objectId, List<String> historyStepIds, List<String> currentStepIds, List<SolrInputDocument> documents)
132    {
133        SolrInputDocument entryDoc = new SolrInputDocument();
134        
135        String id = objectId + "#workflow";
136        
137        String name = workflow.getWorkflowName(workflowId);
138        int state = workflow.getEntryState(workflowId);
139        
140        entryDoc.addField(ID, id);
141        entryDoc.addField(DOCUMENT_TYPE, TYPE_WF_ENTRY);
142        entryDoc.addField(WORKFLOW_NAME, name);
143        entryDoc.addField(WORKFLOW_ENTRY_STATE, state);
144        entryDoc.addField(WORKFLOW_HISTORY_STEPS_DV, historyStepIds);
145        entryDoc.addField(WORKFLOW_CURRENT_STEPS_DV, currentStepIds);
146        
147        documents.add(entryDoc);
148    }
149    
150    private List<String> indexSteps(List<Step> steps, String objectId, List<SolrInputDocument> documents)
151    {
152        List<String> ids = new ArrayList<>();
153        
154        for (Step step : steps)
155        {
156            String id = indexStep(step, objectId, documents);
157            ids.add(id);
158        }
159        
160        return ids;
161    }
162
163    private String indexStep(Step step, String objectId, List<SolrInputDocument> documents)
164    {
165        SolrInputDocument entryDoc = new SolrInputDocument();
166        
167        String id = objectId + "#step" + step.getId();
168        int stepId = step.getStepId();
169        int actionId = step.getActionId();
170        String owner = step.getOwner();
171        String caller = step.getCaller();
172        Date startDate = step.getStartDate();
173        Date dueDate = step.getDueDate();
174        Date finishDate = step.getFinishDate();
175        String status = step.getStatus();
176        
177        entryDoc.addField(ID, id);
178        entryDoc.addField(DOCUMENT_TYPE, TYPE_WF_STEP);
179        
180        entryDoc.addField(WORKFLOW_STEP_ID, stepId);
181        entryDoc.addField(WORKFLOW_STEP_ACTIONID, actionId);
182        if (owner != null)
183        {
184            entryDoc.addField(WORKFLOW_STEP_OWNER, owner);
185        }
186        if (caller != null)
187        {
188            entryDoc.addField(WORKFLOW_STEP_CALLER, caller);
189        }
190        if (startDate != null)
191        {
192            entryDoc.addField(WORKFLOW_STEP_STARTDATE, startDate);
193        }
194        if (dueDate != null)
195        {
196            entryDoc.addField(WORKFLOW_STEP_DUEDATE, dueDate);
197        }
198        if (finishDate != null)
199        {
200            entryDoc.addField(WORKFLOW_STEP_FINISHDATE, finishDate);
201        }
202        if (status != null)
203        {
204            entryDoc.addField(WORKFLOW_STEP_STATUS, status);
205        }
206        
207        documents.add(entryDoc);
208        
209        return id;
210    }
211    
212    /**
213     * Unindex the workflow history of an Ametys object.
214     * @param objectId The Ametys object ID.
215     * @param workspaceName The workspace where to work in 
216     * @param solrClient The solr client to use
217     * @throws SolrServerException If a solr error occurs.
218     * @throws IOException If an I/O error occurs while indexing.
219     */
220    public void unindexAmetysObjectWorkflow(String objectId, String workspaceName, SolrClient solrClient) throws SolrServerException, IOException
221    {
222        if (_enabled)
223        {
224            // _documentType:(workflowEntry OR workflowStep) AND id:content\://xxx#*
225            StringBuilder query = new StringBuilder();
226            query.append(DOCUMENT_TYPE).append(":(")
227            .append(TYPE_WF_ENTRY).append(" OR ").append(TYPE_WF_STEP)
228            .append(") AND id:").append(ClientUtils.escapeQueryChars(objectId + "#")).append("*");
229            
230            solrClient.deleteByQuery(_solrClientProvider.getCollectionName(workspaceName), query.toString());
231        }
232    }
233    
234}