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