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