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}