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.indexing.solr;
017
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import javax.jcr.Repository;
025import javax.jcr.RepositoryException;
026import javax.jcr.Session;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.solr.client.solrj.SolrClient;
038import org.apache.solr.client.solrj.SolrServerException;
039
040import org.ametys.cms.content.archive.ArchiveConstants;
041import org.ametys.cms.content.indexing.solr.SolrIndexer;
042import org.ametys.cms.indexing.IndexingException;
043import org.ametys.cms.indexing.WorkspaceIndexer;
044import org.ametys.cms.search.solr.SolrClientProvider;
045import org.ametys.core.schedule.progression.ContainerProgressionTracker;
046import org.ametys.core.schedule.progression.ProgressionTrackerFactory;
047import org.ametys.core.schedule.progression.SimpleProgressionTracker;
048import org.ametys.plugins.repository.RepositoryConstants;
049import org.ametys.plugins.repository.provider.AbstractRepository;
050import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
051import org.ametys.plugins.repository.provider.WorkspaceSelector;
052import org.ametys.runtime.i18n.I18nizableText;
053import org.ametys.runtime.plugin.component.AbstractLogEnabled;
054
055/**
056 * Component indexing a workspace in a Solr server.
057 */
058public class SolrWorkspaceIndexer extends AbstractLogEnabled implements WorkspaceIndexer, Component, Serviceable, Contextualizable
059{
060    /** The repository. */
061    protected Repository _repository;
062    
063    /** The solr indexer. */
064    protected SolrIndexer _solrIndexer;
065    
066    /** The workspace selector. */
067    protected WorkspaceSelector _workspaceSelector;
068    
069    /** Additional documents provider extension point. */
070    protected DocumentProviderExtensionPoint _docProviderEP;
071    
072    /** The Solr client provider */
073    protected SolrClientProvider _solrClientProvider;
074    
075    /** The component context. */
076    protected Context _context;
077
078    @Override
079    public void service(ServiceManager manager) throws ServiceException
080    {
081        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
082        _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE);
083        _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE);
084        _docProviderEP = (DocumentProviderExtensionPoint) manager.lookup(DocumentProviderExtensionPoint.ROLE);
085        _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE);
086    }
087    
088    @Override
089    public void contextualize(Context context) throws ContextException
090    {
091        _context = context;
092    }
093    
094    @Override
095    public String[] getWorkspacesNames() throws RepositoryException
096    {
097        Session session = _repository.login();
098        String[] workspaceNames = session.getWorkspace().getAccessibleWorkspaceNames();
099        return _sortWorkspacesNames(workspaceNames);
100    }
101    
102    /**
103     * Sort the workspaces names to place the default and archive workspaces at the end
104     * @param workspacesNames The workspaces names to sort
105     * @return The workspacesNames sorted
106     */
107    protected String[] _sortWorkspacesNames(String [] workspacesNames)
108    {
109        List<String> sortedWorkspacesNames = new ArrayList<>();
110      
111        for (String workspaceName : workspacesNames)
112        {
113            // Add these two at the end of the list to priorize "live" workspace if it exists
114            if (RepositoryConstants.DEFAULT_WORKSPACE.equals(workspaceName) || ArchiveConstants.ARCHIVE_WORKSPACE.equals(workspaceName))
115            {
116                sortedWorkspacesNames.add(workspaceName);
117            }
118            else
119            {
120                sortedWorkspacesNames.add(0, workspaceName);
121            }
122        }
123      
124        return sortedWorkspacesNames.toArray(String[]::new);
125    }
126    
127    @Override
128    public void indexAllWorkspaces() throws IndexingException
129    {
130        indexAllWorkspaces(ProgressionTrackerFactory.createContainerProgressionTracker("Index all workspaces", getLogger()));
131    }
132    
133    @Override
134    public void indexAllWorkspaces(ContainerProgressionTracker progressionTracker) throws IndexingException
135    {
136        try
137        {
138            // Get all the workspaces and reindex them.
139            String[] workspaceNames = getWorkspacesNames();
140            
141            // Create steps of the progression tracker, one for the schema and one by workspace
142            SimpleProgressionTracker schemaStep = progressionTracker.addSimpleStep("schema", new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_PREPARING_STEP_LABEL"));
143            for (String workspaceName : workspaceNames)
144            {
145                progressionTracker.addContainerStep(workspaceName, new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_WORKSPACES_STEPS_LABEL", List.of(workspaceName)), 1000);
146            }
147            
148            // Send the schema
149            _solrIndexer.sendSchema();
150            schemaStep.increment();
151            
152            for (String workspaceName : workspaceNames)
153            {
154                _forceWorkspaceAndDoIndex(workspaceName, progressionTracker.getStep(workspaceName)); // no need to ensure core exists, nor sending schema, as it was done just before
155            }
156        }
157        catch (IOException | SolrServerException e)
158        {
159            getLogger().error("Error while sending schema.", e);
160            throw new IndexingException("Error while sending schema.", e);
161        }
162        catch (RepositoryException e)
163        {
164            getLogger().error("Error while indexing the workspaces.", e);
165            throw new IndexingException("Error while indexing the workspaces.", e);
166        }
167    }
168    
169    @Override
170    public void index(String workspaceName) throws IndexingException
171    {
172        index(workspaceName, ProgressionTrackerFactory.createContainerProgressionTracker("Index workspace " + workspaceName, getLogger()));
173    }
174    
175    @Override
176    public void index(String workspaceName, ContainerProgressionTracker progressionTracker) throws IndexingException
177    {
178        // Send the schema
179        try
180        {
181            _solrIndexer.sendSchema();
182        }
183        catch (IOException | SolrServerException e)
184        {
185            getLogger().error("Error while sending schema.", e);
186            throw new IndexingException("Error while sending schema.", e);
187        }
188        
189        _forceWorkspaceAndDoIndex(workspaceName, progressionTracker);
190    }
191    
192    private void _forceWorkspaceAndDoIndex(String workspaceName, ContainerProgressionTracker progressionTracker) throws IndexingException
193    {
194        getLogger().info("Start indexing workspace {}...", workspaceName);
195        
196        Request request = ContextHelper.getRequest(_context);
197        
198        // Retrieve the current workspace.
199        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
200        
201        try
202        {
203            // Force the workspace.
204            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName);
205            
206            _solrIndexer.createCore(workspaceName);
207            doIndex(workspaceName, progressionTracker);
208        }
209        catch (IOException | SolrServerException e)
210        {
211            String msg = String.format("Error while checking if core '%s' exists.", workspaceName);
212            getLogger().error(msg, e);
213            throw new IndexingException(msg, e);
214        }
215        finally
216        {
217            // Restore context
218            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
219        }
220        
221        getLogger().info("Successfully indexed workspace {}", workspaceName);
222    }
223    
224    /**
225     * Index the given workspace.
226     * @param workspaceName The workspace name.
227     * @throws IndexingException If an error occurs indexing the workspace.
228     */
229    protected void doIndex(String workspaceName) throws IndexingException
230    {
231        doIndex(workspaceName, ProgressionTrackerFactory.createContainerProgressionTracker("Indexation of " + workspaceName, getLogger()));
232    }
233    
234    private void _createProgressionTrackerStepsForDoIndex(ContainerProgressionTracker progressionTracker)
235    {
236        progressionTracker.addSimpleStep("unindexing", new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_UNINDEXING_DOCUMENTS_STEP_LABEL"));
237
238        progressionTracker.addSimpleStep("contents", new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_CONTENT_STEP_LABEL"));
239        
240        progressionTracker.addSimpleStep("resources", new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_RESOURCES_STEP_LABEL"));
241         
242        _createAdditionalDocumentsProgressionTrackerSteps(progressionTracker);
243        
244        progressionTracker.addSimpleStep("commitoptimize", new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_SAVING_COMMIT_AND_OPTIMIZE_STEP_LABEL"));
245    }
246    
247    /**
248     * Index the given workspace.
249     * @param workspaceName The workspace name.
250     * @param progressionTracker The progression of the indexation
251     * @throws IndexingException If an error occurs indexing the workspace.
252     */
253    protected void doIndex(String workspaceName, ContainerProgressionTracker progressionTracker) throws IndexingException
254    {
255        _createProgressionTrackerStepsForDoIndex(progressionTracker);
256        
257        try
258        {
259            SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName, false);
260            
261            // First, unindex all documents.
262            _solrIndexer.unindexAllDocuments(workspaceName, solrClient);
263            ((SimpleProgressionTracker) progressionTracker.getStep("unindexing")).increment();
264            
265            // Index all contents.
266            _solrIndexer.indexAllContents(workspaceName, true, solrClient, progressionTracker.getStep("contents"));
267            
268            // Index all resources.
269            _solrIndexer.indexAllResources(workspaceName, solrClient, progressionTracker.getStep("resources"));
270            
271            // Eventually index additional documents.
272            indexAdditionalDocuments(workspaceName, solrClient, progressionTracker);
273            
274            // Commit changes
275            _solrIndexer.commit(workspaceName, solrClient);
276
277            // When done indexing, optimize.
278            _solrIndexer.optimize(workspaceName, solrClient);
279            ((SimpleProgressionTracker) progressionTracker.getStep("commitoptimize")).increment();
280        }
281        catch (Exception e)
282        {
283            getLogger().error("Error indexing the workspace '" + workspaceName + "'.", e);
284            throw new IndexingException("Error indexing the workspace '" + workspaceName + "'.", e);
285        }
286    }
287    
288    /**
289     * Index additional documents provided by the extensions.
290     * @param workspaceName The workspace name.
291     * @param solrClient The solr client to use
292     * @throws IndexingException If an error occurs while indexing.
293     */
294    protected void indexAdditionalDocuments(String workspaceName, SolrClient solrClient) throws IndexingException
295    {
296        indexAdditionalDocuments(workspaceName, solrClient, ProgressionTrackerFactory.createContainerProgressionTracker("Index additional documents for workspace " + workspaceName, getLogger()));
297    }
298    
299    /**
300     * Create the progression tracker steps for additional documents
301     * @param progressionTracker The progression tracker
302     */
303    protected void _createAdditionalDocumentsProgressionTrackerSteps(ContainerProgressionTracker progressionTracker)
304    {
305        Set<String> extensionsIds = _docProviderEP.getExtensionsIds();
306        
307        for (String id : extensionsIds)
308        {
309            DocumentProvider extension = _docProviderEP.getExtension(id);
310            progressionTracker.addContainerStep("documents-" + id, new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_DOCUMENTS_PROVIDER_STEPS_LABEL", Map.of("0", extension.getLabel())));
311        }
312    }
313    
314    /**
315     * Index additional documents provided by the extensions.
316     * @param workspaceName The workspace name.
317     * @param solrClient The solr client to use
318     * @param progressionTracker The progression of the indexation
319     * @throws IndexingException If an error occurs while indexing.
320     */
321    protected void indexAdditionalDocuments(String workspaceName, SolrClient solrClient, ContainerProgressionTracker progressionTracker) throws IndexingException
322    {
323        Set<String> extensionsIds = _docProviderEP.getExtensionsIds();
324        
325        for (String id : extensionsIds)
326        {
327            DocumentProvider docProvider = _docProviderEP.getExtension(id);
328            docProvider.indexDocuments(workspaceName, solrClient, progressionTracker.getStep("documents-" + id));
329        }
330    }
331}