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}