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