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        SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName, true);
073        indexAmetysObjectWorkflow(object, workspaceName, solrClient);
074    }
075    
076    /**
077     * Index the workflow history of an Ametys object.
078     * @param object The ametys object.
079     * @param workspaceName The workspace where to work in
080     * @param solrClient The solr client to use
081     * @throws SolrServerException If a solr error occurs.
082     * @throws IOException If an I/O error occurs while indexing.
083     */
084    public void indexAmetysObjectWorkflow(WorkflowAwareAmetysObject object, String workspaceName, SolrClient solrClient) throws SolrServerException, IOException
085    {
086        long time_0 = System.currentTimeMillis();
087        getLogger().debug("Indexing the workflow of {}", object);
088        
089        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(object);
090        
091        long workflowId = object.getWorkflowId();
092        List<Step> currentSteps = workflow.getCurrentSteps(workflowId);
093        List<Step> historySteps = workflow.getHistorySteps(workflowId);
094        
095        String objectId = object.getId();
096        
097        List<SolrInputDocument> documents = new ArrayList<>();
098        
099        List<String> historyStepIds = indexSteps(historySteps, objectId, documents);
100        List<String> currentStepIds = indexSteps(currentSteps, objectId, documents);
101        
102        indexWorkflowEntry(workflow, workflowId, objectId, historyStepIds, currentStepIds, documents);
103        
104        String collection = _solrClientProvider.getCollectionName(workspaceName);
105        UpdateResponse solrResponse = solrClient.add(collection, documents);
106        int status = solrResponse.getStatus();
107        
108        if (status != 0)
109        {
110            throw new IOException("Workflow history indexation error (status code=" + status + ")");
111        }
112        
113        getLogger().debug("Successfully indexed workflow of {} in {} ms", object, System.currentTimeMillis() - time_0);
114    }
115    
116    private void indexWorkflowEntry(AmetysObjectWorkflow workflow, long workflowId, String objectId, List<String> historyStepIds, List<String> currentStepIds, List<SolrInputDocument> documents)
117    {
118        SolrInputDocument entryDoc = new SolrInputDocument();
119        
120        String id = objectId + "#workflow";
121        
122        String name = workflow.getWorkflowName(workflowId);
123        int state = workflow.getEntryState(workflowId);
124        
125        entryDoc.addField(ID, id);
126        entryDoc.addField(DOCUMENT_TYPE, TYPE_WF_ENTRY);
127        entryDoc.addField(WORKFLOW_NAME, name);
128        entryDoc.addField(WORKFLOW_ENTRY_STATE, state);
129        entryDoc.addField(WORKFLOW_HISTORY_STEPS_DV, historyStepIds);
130        entryDoc.addField(WORKFLOW_CURRENT_STEPS_DV, currentStepIds);
131        
132        documents.add(entryDoc);
133    }
134    
135    private List<String> indexSteps(List<Step> steps, String objectId, List<SolrInputDocument> documents)
136    {
137        List<String> ids = new ArrayList<>();
138        
139        for (Step step : steps)
140        {
141            String id = indexStep(step, objectId, documents);
142            ids.add(id);
143        }
144        
145        return ids;
146    }
147
148    private String indexStep(Step step, String objectId, List<SolrInputDocument> documents)
149    {
150        SolrInputDocument entryDoc = new SolrInputDocument();
151        
152        String id = objectId + "#step" + step.getId();
153        int stepId = step.getStepId();
154        int actionId = step.getActionId();
155        String owner = step.getOwner();
156        String caller = step.getCaller();
157        Date startDate = step.getStartDate();
158        Date dueDate = step.getDueDate();
159        Date finishDate = step.getFinishDate();
160        String status = step.getStatus();
161        
162        entryDoc.addField(ID, id);
163        entryDoc.addField(DOCUMENT_TYPE, TYPE_WF_STEP);
164        
165        entryDoc.addField(WORKFLOW_STEP_ID, stepId);
166        entryDoc.addField(WORKFLOW_STEP_ACTIONID, actionId);
167        if (owner != null)
168        {
169            entryDoc.addField(WORKFLOW_STEP_OWNER, owner);
170        }
171        if (caller != null)
172        {
173            entryDoc.addField(WORKFLOW_STEP_CALLER, caller);
174        }
175        if (startDate != null)
176        {
177            entryDoc.addField(WORKFLOW_STEP_STARTDATE, startDate);
178        }
179        if (dueDate != null)
180        {
181            entryDoc.addField(WORKFLOW_STEP_DUEDATE, dueDate);
182        }
183        if (finishDate != null)
184        {
185            entryDoc.addField(WORKFLOW_STEP_FINISHDATE, finishDate);
186        }
187        if (status != null)
188        {
189            entryDoc.addField(WORKFLOW_STEP_STATUS, status);
190        }
191        
192        documents.add(entryDoc);
193        
194        return id;
195    }
196    
197    /**
198     * Unindex the workflow history of an Ametys object.
199     * @param objectId The Ametys object ID.
200     * @param workspaceName The workspace where to work in 
201     * @param solrClient The solr client to use
202     * @throws SolrServerException If a solr error occurs.
203     * @throws IOException If an I/O error occurs while indexing.
204     */
205    public void unindexAmetysObjectWorkflow(String objectId, String workspaceName, SolrClient solrClient) throws SolrServerException, IOException
206    {
207        // _documentType:(workflowEntry OR workflowStep) AND id:content\://xxx#*
208        StringBuilder query = new StringBuilder();
209        query.append(DOCUMENT_TYPE).append(":(")
210             .append(TYPE_WF_ENTRY).append(" OR ").append(TYPE_WF_STEP)
211             .append(") AND id:").append(ClientUtils.escapeQueryChars(objectId + "#")).append("*");
212        
213        solrClient.deleteByQuery(_solrClientProvider.getCollectionName(workspaceName), query.toString());
214    }
215    
216}