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.Set;
020
021import javax.jcr.Repository;
022import javax.jcr.RepositoryException;
023import javax.jcr.Session;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.cocoon.environment.Request;
034import org.apache.solr.client.solrj.SolrClient;
035import org.apache.solr.client.solrj.SolrServerException;
036
037import org.ametys.cms.content.indexing.solr.SolrIndexer;
038import org.ametys.cms.indexing.IndexingException;
039import org.ametys.cms.indexing.WorkspaceIndexer;
040import org.ametys.cms.search.solr.SolrClientProvider;
041import org.ametys.core.ui.Callable;
042import org.ametys.plugins.repository.RepositoryConstants;
043import org.ametys.plugins.repository.provider.AbstractRepository;
044import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
045import org.ametys.plugins.repository.provider.WorkspaceSelector;
046import org.ametys.runtime.plugin.component.AbstractLogEnabled;
047
048/**
049 * Component indexing a workspace in a Solr server.
050 */
051public class SolrWorkspaceIndexer extends AbstractLogEnabled implements WorkspaceIndexer, Component, Serviceable, Contextualizable
052{
053    /** The repository. */
054    protected Repository _repository;
055    
056    /** The solr indexer. */
057    protected SolrIndexer _solrIndexer;
058    
059    /** The workspace selector. */
060    protected WorkspaceSelector _workspaceSelector;
061    
062    /** Additional documents provider extension point. */
063    protected DocumentProviderExtensionPoint _docProviderEP;
064    
065    /** The Solr client provider */
066    protected SolrClientProvider _solrClientProvider;
067    
068    /** The component context. */
069    protected Context _context;
070    
071    @Override
072    public void service(ServiceManager manager) throws ServiceException
073    {
074        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
075        _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE);
076        _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE);
077        _docProviderEP = (DocumentProviderExtensionPoint) manager.lookup(DocumentProviderExtensionPoint.ROLE);
078        _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE);
079    }
080    
081    @Override
082    public void contextualize(Context context) throws ContextException
083    {
084        _context = context;
085    }
086    
087    @Override
088    @Callable
089    public void indexAllWorkspaces() throws IndexingException
090    {
091        String[] workspaceNames;
092        try
093        {
094            // Get all the workspaces and reindex them.
095            Session session = _repository.login();
096            workspaceNames = session.getWorkspace().getAccessibleWorkspaceNames();
097        }
098        catch (RepositoryException e)
099        {
100            getLogger().error("Error while indexing the workspaces.", e);
101            throw new IndexingException("Error while indexing the workspaces.", e);
102        }
103        
104        // Before sending schema, all cores need to exist
105        ensureCoresExists(workspaceNames);
106        
107        _sendSchema();
108        
109        for (String workspaceName : workspaceNames)
110        {
111            _index(workspaceName, false, false); // no need to ensure core exists, nor sending schema, as it was done just before
112        }
113    }
114    
115    // Send schema (using default update Solr client)
116    private void _sendSchema() throws IndexingException
117    {
118        Request request = ContextHelper.getRequest(_context);
119        // Retrieve the current workspace.
120        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
121        try
122        {
123            // Force the default workspace.
124            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
125            _solrIndexer.sendSchema();
126        }
127        catch (IOException | SolrServerException e)
128        {
129            getLogger().error("Error while sending schema.", e);
130            throw new IndexingException("Error while sending schema.", e);
131        }
132        finally
133        {
134            // Restore context
135            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
136        }
137    }
138    
139    @Override
140    public void index(String workspaceName) throws IndexingException
141    {
142        _index(workspaceName, true, true);
143    }
144    
145    private void _index(String workspaceName, boolean ensureCoreExists, boolean sendSchema) throws IndexingException
146    {
147        if (ensureCoreExists)
148        {
149            // Create the core corresponding to the workspace if it doesn't exist.
150            ensureCoreExists(workspaceName);
151        }
152        
153        getLogger().info("Start indexing workspace {}...", workspaceName);
154        
155        _forceWorkspaceAndDoIndex(workspaceName, sendSchema);
156        
157        getLogger().info("Successfully indexed workspace {}", workspaceName);
158    }
159    
160    private void _forceWorkspaceAndDoIndex(String workspaceName, boolean sendSchema) throws IndexingException
161    {
162        Request request = ContextHelper.getRequest(_context);
163        
164        // Retrieve the current workspace.
165        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
166        
167        try
168        {
169            // Force the workspace.
170            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName);
171            
172            if (sendSchema)
173            {
174                try
175                {
176                    _solrIndexer.sendSchema();
177                }
178                catch (IOException | SolrServerException e)
179                {
180                    getLogger().error("Error while sending schema.", e);
181                    throw new IndexingException("Error while sending schema.", e);
182                }
183            }
184            
185            doIndex(workspaceName);
186        }
187        finally
188        {
189            // Restore context
190            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
191        }
192        
193    }
194    
195    /**
196     * Index the given workspace.
197     * @param workspaceName The workspace name.
198     * @throws IndexingException If an error occurs indexing the workspace.
199     */
200    protected void doIndex(String workspaceName) throws IndexingException
201    {
202        try
203        {
204            SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName, false);
205            
206            // First, unindex all documents.
207            _solrIndexer.unindexAllDocuments(workspaceName, solrClient);
208            
209            // Index all contents.
210            _solrIndexer.indexAllContents(workspaceName, true, solrClient);
211            
212            // Index all resources.
213            _solrIndexer.indexAllResources(workspaceName, solrClient);
214            
215            // Eventually index additional documents.
216            indexAdditionalDocuments(workspaceName, solrClient);
217            
218            // Commit changes
219            _solrIndexer.commit(workspaceName, solrClient);
220            // When done indexing, optimize.
221            _solrIndexer.optimize(workspaceName, solrClient);
222        }
223        catch (Exception e)
224        {
225            getLogger().error("Error indexing the workspace '" + workspaceName + "'.", e);
226            throw new IndexingException("Error indexing the workspace '" + workspaceName + "'.", e);
227        }
228    }
229    
230    /**
231     * Index additional documents provided by the extensions.
232     * @param workspaceName The workspace name.
233     * @param solrClient The solr client to use
234     * @throws IndexingException If an error occurs while indexing.
235     */
236    protected void indexAdditionalDocuments(String workspaceName, SolrClient solrClient) throws IndexingException
237    {
238        for (String id : _docProviderEP.getExtensionsIds())
239        {
240            DocumentProvider docProvider = _docProviderEP.getExtension(id);
241            docProvider.indexDocuments(workspaceName, solrClient);
242        }
243    }
244    
245    /**
246     * Create the given cores if they do not exist.
247     * @param coreNames The core names.
248     * @throws IndexingException If an error occurs while checking if cores exist.
249     */
250    protected void ensureCoresExists(String[] coreNames) throws IndexingException
251    {
252        for (String coreName : coreNames)
253        {
254            ensureCoreExists(coreName);
255        }
256    }
257    
258    /**
259     * Create the given core if it doesn't exist.
260     * @param coreName The core name.
261     * @throws IndexingException If an error occurs while checking if core exists.
262     */
263    protected void ensureCoreExists(String coreName) throws IndexingException
264    {
265        try
266        {
267            Set<String> coreNames = _solrIndexer.getCoreNames();
268            if (!coreNames.contains(coreName))
269            {
270                _solrIndexer.createCore(coreName);
271            }
272        }
273        catch (IOException | SolrServerException e)
274        {
275            String msg = String.format("Error while checking if core '%s' exists.", coreName);
276            getLogger().error(msg, e);
277            throw new IndexingException(msg, e);
278        }
279    }
280}