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.content.indexing.solr; 017 018import java.io.IOException; 019import java.nio.ByteBuffer; 020import java.nio.CharBuffer; 021import java.nio.charset.CharsetDecoder; 022import java.nio.charset.CodingErrorAction; 023import java.nio.charset.StandardCharsets; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.Comparator; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Optional; 035import java.util.Set; 036import java.util.concurrent.Future; 037import java.util.function.Function; 038import java.util.stream.Collectors; 039import java.util.stream.StreamSupport; 040 041import javax.jcr.RepositoryException; 042 043import org.apache.avalon.framework.activity.Initializable; 044import org.apache.avalon.framework.component.Component; 045import org.apache.avalon.framework.context.Context; 046import org.apache.avalon.framework.context.ContextException; 047import org.apache.avalon.framework.context.Contextualizable; 048import org.apache.avalon.framework.service.ServiceException; 049import org.apache.avalon.framework.service.ServiceManager; 050import org.apache.avalon.framework.service.Serviceable; 051import org.apache.cocoon.Constants; 052import org.apache.cocoon.components.ContextHelper; 053import org.apache.cocoon.environment.Request; 054import org.apache.commons.collections4.IterableUtils; 055import org.apache.commons.lang3.ObjectUtils; 056import org.apache.commons.lang3.StringUtils; 057import org.apache.solr.client.solrj.SolrClient; 058import org.apache.solr.client.solrj.SolrResponse; 059import org.apache.solr.client.solrj.SolrServerException; 060import org.apache.solr.client.solrj.request.CoreAdminRequest; 061import org.apache.solr.client.solrj.request.CoreAdminRequest.Create; 062import org.apache.solr.client.solrj.request.schema.FieldTypeDefinition; 063import org.apache.solr.client.solrj.request.schema.SchemaRequest; 064import org.apache.solr.client.solrj.request.schema.SchemaRequest.Update; 065import org.apache.solr.client.solrj.response.CoreAdminResponse; 066import org.apache.solr.client.solrj.response.SolrResponseBase; 067import org.apache.solr.client.solrj.response.UpdateResponse; 068import org.apache.solr.client.solrj.response.schema.SchemaRepresentation; 069import org.apache.solr.client.solrj.response.schema.SchemaResponse; 070import org.apache.solr.client.solrj.util.ClientUtils; 071import org.apache.solr.common.SolrInputDocument; 072import org.apache.solr.common.params.CoreAdminParams; 073import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction; 074import org.apache.solr.common.params.ModifiableSolrParams; 075import org.apache.solr.common.params.SolrParams; 076import org.apache.solr.common.util.NamedList; 077import org.slf4j.Logger; 078 079import org.ametys.cms.data.ametysobject.IndexableAmetysObject; 080import org.ametys.cms.indexing.IndexingException; 081import org.ametys.cms.indexing.solr.AbstractIndexerCallable; 082import org.ametys.cms.indexing.solr.AdditionalDataIndexer; 083import org.ametys.cms.indexing.solr.AdditionalDataIndexerExtensionPoint; 084import org.ametys.cms.indexing.solr.IndexationResult; 085import org.ametys.cms.indexing.solr.ReloadAclCacheRequest; 086import org.ametys.cms.indexing.solr.ThreadIndexerHelper; 087import org.ametys.cms.indexing.solr.UpdateAclCacheRequest; 088import org.ametys.cms.indexing.solr.UpdateCorePropertyRequest; 089import org.ametys.cms.model.CMSDataContext; 090import org.ametys.cms.model.properties.Property; 091import org.ametys.cms.repository.Content; 092import org.ametys.cms.repository.ContentQueryHelper; 093import org.ametys.cms.repository.WorkflowAwareContent; 094import org.ametys.cms.rights.solrchecking.ReadAccessHelper; 095import org.ametys.cms.search.model.IndexationAwareElementDefinition; 096import org.ametys.cms.search.model.SystemProperty; 097import org.ametys.cms.search.query.ContentAttachmentQuery; 098import org.ametys.cms.search.query.DocumentTypeQuery; 099import org.ametys.cms.search.query.OrQuery; 100import org.ametys.cms.search.query.Query; 101import org.ametys.cms.search.query.ResourceLocationQuery; 102import org.ametys.cms.search.solr.NoAutoCommitUpdateClient; 103import org.ametys.cms.search.solr.SolrClientProvider; 104import org.ametys.cms.search.solr.schema.SchemaDefinition; 105import org.ametys.cms.search.solr.schema.SchemaDefinitionProvider; 106import org.ametys.cms.search.solr.schema.SchemaDefinitionProviderExtensionPoint; 107import org.ametys.cms.search.solr.schema.SchemaFields; 108import org.ametys.cms.search.solr.schema.SchemaHelper; 109import org.ametys.cms.trash.element.DefaultTrashElement; 110import org.ametys.cms.trash.element.TrashElementFactory; 111import org.ametys.core.group.GroupIdentity; 112import org.ametys.core.right.AllowedUsers; 113import org.ametys.core.schedule.progression.ContainerProgressionTracker; 114import org.ametys.core.schedule.progression.ProgressionTrackerFactory; 115import org.ametys.core.schedule.progression.SimpleProgressionTracker; 116import org.ametys.core.user.UserIdentity; 117import org.ametys.plugins.explorer.resources.Resource; 118import org.ametys.plugins.explorer.resources.ResourceCollection; 119import org.ametys.plugins.repository.AmetysObject; 120import org.ametys.plugins.repository.AmetysObjectIterable; 121import org.ametys.plugins.repository.AmetysObjectResolver; 122import org.ametys.plugins.repository.RepositoryConstants; 123import org.ametys.plugins.repository.TraversableAmetysObject; 124import org.ametys.plugins.repository.UnknownAmetysObjectException; 125import org.ametys.plugins.repository.provider.AbstractRepository; 126import org.ametys.plugins.repository.provider.JackrabbitRepository; 127import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 128import org.ametys.plugins.repository.provider.WorkspaceSelector; 129import org.ametys.plugins.repository.query.QueryHelper; 130import org.ametys.runtime.config.Config; 131import org.ametys.runtime.i18n.I18nizableText; 132import org.ametys.runtime.model.ModelItem; 133import org.ametys.runtime.plugin.component.AbstractLogEnabled; 134 135/** 136 * Solr indexer. 137 */ 138public class SolrIndexer extends AbstractLogEnabled implements Component, Serviceable, Initializable, Contextualizable 139{ 140 /** The component role. */ 141 public static final String ROLE = SolrIndexer.class.getName(); 142 143 private static final String _CONFIGSET_NAME_PREFIX = "configset-"; 144 145 private static final List<String> _READ_ONLY_FIELDS = Arrays.asList("id", "_version_", "_text_"); 146 private static final List<String> _READ_ONLY_FIELDTYPES = Arrays.asList("string", "plong", "text_general"); 147 148 private static final int __SOLR_STRING_NB_BYTES_LIMIT = 32766; 149 150 /** The service manager. */ 151 protected ServiceManager _manager; 152 /** The ametys object resolver. */ 153 protected AmetysObjectResolver _resolver; 154 /** The schema definition provider extension point. */ 155 protected SchemaDefinitionProviderExtensionPoint _schemaDefProviderEP; 156 /** The schema helper. */ 157 protected SchemaHelper _schemaHelper; 158 /** Solr Ametys contents indexer */ 159 protected SolrContentIndexer _solrContentIndexer; 160 /** Solr workflow indexer. */ 161 protected SolrWorkflowIndexer _solrWorkflowIndexer; 162 /** Solr resource indexer. */ 163 protected SolrResourceIndexer _solrResourceIndexer; 164 /** Solr trash element indexer. */ 165 protected SolrTrashElementIndexer _solrTrashElementIndexer; 166 167 /** The Solr client provider */ 168 protected SolrClientProvider _solrClientProvider; 169 170 /** The solr core prefix. */ 171 protected String _solrCorePrefix; 172 /** The Ametys internal URL used by Solr to query Ametys */ 173 protected String _ametysInternalUrl; 174 175 /** The workspace selector. */ 176 protected WorkspaceSelector _workspaceSelector; 177 /** The JCR repository */ 178 protected JackrabbitRepository _repository; 179 /** The helper for read access */ 180 protected ReadAccessHelper _readAccessHelper; 181 /** The thread indexer helper */ 182 protected ThreadIndexerHelper _threadIndexerHelper; 183 184 /** The avalon context */ 185 protected Context _context; 186 /** Cocoon Context */ 187 protected org.apache.cocoon.environment.Context _cocoonContext; 188 189 /** The additional data indexer extension point */ 190 protected AdditionalDataIndexerExtensionPoint _additionalDataIndexerEP; 191 192 /** 193 * Truncates (if needed) the given string in order to be indexed without <i>immense term</i> error by Solr. 194 * Only the {@value #__SOLR_STRING_NB_BYTES_LIMIT} first bytes of the String will be kept. 195 * @param value The string value to index 196 * @param logger The logger for logging in WARN level in case the given string is too long and will be truncated. Can be null if you do not want to log. 197 * @param documentId The id of the document being indexed. Can be null if you do not want to log. 198 * @param fieldName The name of the field being indexed. Can be null if you do not want to log. 199 * @return The given string value, or its truncation if it is too long (greater than {@value #__SOLR_STRING_NB_BYTES_LIMIT} bytes) 200 */ 201 public static String truncateUtf8StringValue(String value, Logger logger, String documentId , String fieldName) 202 { 203 if (value.length() * 4 <= __SOLR_STRING_NB_BYTES_LIMIT) 204 { 205 // With UTF-8, a character is encoded using 1, 2, 3 or 4 bytes, so (value.length() <= value.getBytes().length <= 4 * value.length()) 206 // As a result, value.getBytes().length <= limit 207 return value; 208 } 209 210 // There is a doubt, the string may need to be truncated (or not) 211 byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8); 212 int bytesLength = valueBytes.length; 213 if (bytesLength <= __SOLR_STRING_NB_BYTES_LIMIT) 214 { 215 return value; 216 } 217 218 if (ObjectUtils.allNotNull(logger, documentId, fieldName)) 219 { 220 logger.warn("The string value for document '{}' and field name '{}' is longer ({}) than the max bytes length {}. It will be truncated to prevent Solr error, but you should consider verifying why this string is so long.", documentId, fieldName, bytesLength, __SOLR_STRING_NB_BYTES_LIMIT); 221 } 222 223 // Need a truncation (inspired by https://stackoverflow.com/questions/119328/how-do-i-truncate-a-java-string-to-fit-in-a-given-number-of-bytes-once-utf-8-en#answer-35148974) 224 CharBuffer charBuffer = CharBuffer.allocate(__SOLR_STRING_NB_BYTES_LIMIT); 225 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder() 226 .onMalformedInput(CodingErrorAction.IGNORE); 227 decoder.decode(ByteBuffer.wrap(valueBytes, 0, __SOLR_STRING_NB_BYTES_LIMIT), charBuffer, true); 228 decoder.flush(charBuffer); 229 return new String(charBuffer.array(), 0, charBuffer.position()); 230 } 231 232 @Override 233 public void service(ServiceManager serviceManager) throws ServiceException 234 { 235 _manager = serviceManager; 236 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 237 _schemaDefProviderEP = (SchemaDefinitionProviderExtensionPoint) serviceManager.lookup(SchemaDefinitionProviderExtensionPoint.ROLE); 238 _schemaHelper = (SchemaHelper) serviceManager.lookup(SchemaHelper.ROLE); 239 _solrContentIndexer = (SolrContentIndexer) serviceManager.lookup(SolrContentIndexer.ROLE); 240 _solrWorkflowIndexer = (SolrWorkflowIndexer) serviceManager.lookup(SolrWorkflowIndexer.ROLE); 241 _solrResourceIndexer = (SolrResourceIndexer) serviceManager.lookup(SolrResourceIndexer.ROLE); 242 _solrTrashElementIndexer = (SolrTrashElementIndexer) serviceManager.lookup(SolrTrashElementIndexer.ROLE); 243 _solrClientProvider = (SolrClientProvider) serviceManager.lookup(SolrClientProvider.ROLE); 244 _workspaceSelector = (WorkspaceSelector) serviceManager.lookup(WorkspaceSelector.ROLE); 245 _readAccessHelper = (ReadAccessHelper) serviceManager.lookup(ReadAccessHelper.ROLE); 246 _repository = (JackrabbitRepository) serviceManager.lookup(AbstractRepository.ROLE); 247 _threadIndexerHelper = (ThreadIndexerHelper) serviceManager.lookup(ThreadIndexerHelper.ROLE); 248 _additionalDataIndexerEP = (AdditionalDataIndexerExtensionPoint) serviceManager.lookup(AdditionalDataIndexerExtensionPoint.ROLE); 249 } 250 251 @Override 252 public void initialize() throws Exception 253 { 254 Config config = Config.getInstance(); 255 _solrCorePrefix = config.getValue("cms.solr.core.prefix"); 256 257 _ametysInternalUrl = config.getValue("cms.solr.core.ametys.internal.url"); 258 if (StringUtils.isBlank(_ametysInternalUrl)) 259 { 260 // fallback to CMS URL as internal URL is an optional parameter 261 _ametysInternalUrl = config.getValue("cms.url"); 262 } 263 } 264 265 @Override 266 public void contextualize(Context context) throws ContextException 267 { 268 _context = context; 269 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 270 } 271 272 /** 273 * Gets the 'autocommit' Solr client 274 * @param workspaceName The name of the workspace 275 * @return the Solr client 276 */ 277 protected SolrClient _getAutoCommitSolrClient(String workspaceName) 278 { 279 return _solrClientProvider.getUpdateClient(workspaceName, true); 280 } 281 282 /** 283 * Gets the 'no autocommit' Solr client 284 * @param workspaceName The name of the workspace 285 * @return the Solr client 286 */ 287 protected SolrClient _getNoAutoCommitSolrClient(String workspaceName) 288 { 289 return _solrClientProvider.getUpdateClient(workspaceName, false); 290 } 291 292 // for admin operations 293 private SolrClient _defaultSolrClient() 294 { 295 return _solrClientProvider.getUpdateClient(RepositoryConstants.DEFAULT_WORKSPACE); 296 } 297 298 /** 299 * Get the names of the Solr cores. 300 * @return The names of the Solr cores. 301 * @throws IOException If an I/O error occurs. 302 * @throws SolrServerException If a Solr error occurs. 303 */ 304 public Set<String> getCoreNames() throws IOException, SolrServerException 305 { 306 Set<String> coreNames = new HashSet<>(); 307 308 getLogger().debug("Getting core list."); 309 310 CoreAdminRequest req = new CoreAdminRequest(); 311 req.setAction(CoreAdminAction.STATUS); 312 313 NamedList<NamedList<Object>> status = req.process(_defaultSolrClient()).getCoreStatus(); 314 for (Map.Entry<String, NamedList<Object>> core : status) 315 { 316 String fullName = (String) core.getValue().get("name"); 317 if (fullName.startsWith(_solrCorePrefix)) 318 { 319 coreNames.add(fullName.substring(_solrCorePrefix.length())); 320 } 321 } 322 323 return coreNames; 324 } 325 326 /** 327 * Get the names of the Solr cores. 328 * @return The names of the Solr cores. 329 * @throws IOException If an I/O error occurs. 330 * @throws SolrServerException If a Solr error occurs. 331 */ 332 protected Set<String> getRealCoreNames() throws IOException, SolrServerException 333 { 334 Set<String> coreNames = new HashSet<>(); 335 336 getLogger().debug("Getting core list."); 337 338 CoreAdminResponse response = new CoreAdminRequest().process(_defaultSolrClient()); 339 340 NamedList<NamedList<Object>> status = response.getCoreStatus(); 341 for (Map.Entry<String, NamedList<Object>> core : status) 342 { 343 String fullName = (String) core.getValue().get("name"); 344 if (fullName.startsWith(_solrCorePrefix)) 345 { 346 coreNames.add(fullName.substring(_solrCorePrefix.length())); 347 } 348 } 349 350 return coreNames; 351 } 352 353 /** 354 * Create a Solr core. 355 * @param name The name of the core to create. 356 * @throws IOException If an I/O error occurs. 357 * @throws SolrServerException If a Solr error occurs. 358 */ 359 public void createCore(String name) throws IOException, SolrServerException 360 { 361 Set<String> cores = getCoreNames(); 362 if (!cores.contains(name)) 363 { 364 String fullName = _solrCorePrefix + name; 365 String configsetName = _CONFIGSET_NAME_PREFIX + (_solrCorePrefix.endsWith("-") ? _solrCorePrefix.substring(0, _solrCorePrefix.length() - 1) : _solrCorePrefix); 366 _createConfigset(configsetName); 367 368 getLogger().info("Creating core '{}' (full name: '{}').", name, fullName); 369 370 Create createRequest = new Create() { 371 @Override 372 public SolrParams getParams() 373 { 374 ModifiableSolrParams params = new ModifiableSolrParams(); 375 376 params.set(CoreAdminParams.ACTION, CoreAdminAction.CREATE.toString()); 377 params.set(CoreAdminParams.NAME, fullName); 378 params.set(CoreAdminParams.CONFIGSET, configsetName); 379 params.set("property.ametys.url", _ametysInternalUrl); 380 381 return params; 382 } 383 }; 384 385 NamedList<?> results = createRequest.process(_defaultSolrClient()).getResponse(); 386 387 NamedList<?> error = (NamedList<?>) results.get("error"); 388 if (error != null) 389 { 390 throw new IOException("Error creating the core: " + error.get("msg")); 391 } 392 } 393 else 394 { 395 if (getLogger().isDebugEnabled()) 396 { 397 getLogger().debug("Core '" + name + "' already exists, skipping it."); 398 } 399 } 400 } 401 402 /** 403 * Updates the ametys.url property of the Solr cores. 404 */ 405 public void updateAmetysUrlCoreProperty() 406 { 407 Set<String> coreNames; 408 try 409 { 410 coreNames = getCoreNames(); 411 } 412 catch (SolrServerException | IOException e) 413 { 414 getLogger().error("Cannot get Solr core names. As a result, the internal Ametys URL could not be updated on Solr server.", e); 415 return; 416 } 417 418 for (String coreName : coreNames) 419 { 420 String collection = _solrClientProvider.getCollectionName(coreName); 421 SolrResponseBase response; 422 try 423 { 424 response = new UpdateCorePropertyRequest("ametys.url", _ametysInternalUrl).process(_getAutoCommitSolrClient(coreName), collection); 425 } 426 catch (SolrServerException | IOException e) 427 { 428 getLogger().error("'core.properties' file updating for workspace '{}' did not succeed as expected.", coreName, e); 429 continue; 430 } 431 432 NamedList<Object> responseParams = response.getResponse(); 433 if ("ok".equals(responseParams.get("result"))) 434 { 435 Boolean valueChanged = responseParams.getBooleanArg("valueChanged"); 436 if (valueChanged) 437 { 438 getLogger().info("'core.properties' file updated with the up-to-date Ametys URL for workspace '{}'", coreName); 439 } 440 else 441 { 442 getLogger().info("'core.properties' file already has the up-to-date Ametys URL for workspace '{}', it was not modified.", coreName); 443 } 444 } 445 else 446 { 447 getLogger().error("'core.properties' file updating for workspace '{}' did not succeed as expected.", coreName); 448 } 449 } 450 } 451 452 private void _createConfigset(String name) throws IOException, SolrServerException 453 { 454 getLogger().info("Creating (if necessary) configset '{}'", name); 455 456 // This request handler will check if configset exists. If not it will be created. 457 CoreAdminRequest request = new CoreAdminRequest() { 458 @Override 459 public SolrParams getParams() 460 { 461 ModifiableSolrParams params = new ModifiableSolrParams(); 462 params.set(CoreAdminParams.ACTION, "createConfigset"); 463 params.set(CoreAdminParams.NAME, name); 464 return params; 465 } 466 }; 467 468 NamedList<?> results = request.process(_defaultSolrClient()).getResponse(); 469 470 NamedList<?> error = (NamedList<?>) results.get("error"); 471 if (error != null) 472 { 473 throw new IOException("Error creating the core: " + error.get("msg")); 474 } 475 } 476 477 /** 478 * Delete a Solr core. 479 * @param name The name of the core to delete. 480 * @throws IOException If an I/O error occurs. 481 * @throws SolrServerException If a Solr error occurs. 482 */ 483 public void deleteCore(String name) throws IOException, SolrServerException 484 { 485 String fullName = _solrCorePrefix + name; 486 487 getLogger().info("Deleting core '{}' (full name: '{}').", name, fullName); 488 489 CoreAdminResponse response = CoreAdminRequest.unloadCore(fullName, true, true, _defaultSolrClient()); 490 NamedList<?> results = response.getResponse(); 491 492 NamedList<?> error = (NamedList<?>) results.get("error"); 493 if (error != null) 494 { 495 throw new IOException("Error deleting core" + name + ": " + error.get("msg")); 496 } 497 } 498 499 /** 500 * Send the schema. 501 * @throws IOException If a communication error occurs. 502 * @throws SolrServerException If a solr error occurs. 503 */ 504 public void sendSchema() throws IOException, SolrServerException 505 { 506 getLogger().info("Computing and sending the schema to the solr server."); 507 508 String workspaceName = _workspaceSelector.getWorkspace(); 509 String collection = _solrClientProvider.getCollectionName(workspaceName); 510 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 511 512// SchemaRepresentation staticSchema = _schemaHelper.getStaticSchema(); 513 SchemaRepresentation staticSchema = _schemaHelper.getSchema("resource://org/ametys/cms/search/solr/schema/schema.xml"); 514 515 // TODO Clear the schema except fields marked ametysReadOnly="true". 516 517 // Clear the current schema. 518 clearSchema(solrClient, collection); 519 520 SchemaRequest schemaRequest = new SchemaRequest(); 521 SchemaResponse schemaResponse = schemaRequest.process(solrClient, collection); 522 523 // The cleared schema contains only the basic fields which can't be deleted. 524 SchemaRepresentation clearedSchema = schemaResponse.getSchemaRepresentation(); 525 SchemaFields schemaFields = new SchemaFields(clearedSchema); 526 527 getLogger().debug("Schema after clear: \n{}", schemaFields.toString()); 528 529 // Add the static schema types and fields. 530 List<SchemaRequest.Update> updates = new ArrayList<>(); 531 532 // Set "add field" definitions from the static schema to the update list. 533 addStaticSchemaUpdates(updates, staticSchema, schemaFields); 534 535 getLogger().debug("Temporary schema after static add: \n{}", schemaFields.toString()); 536 537 // Set "add field" definitions from the static schema to the update list. 538 addCustomUpdates(updates, schemaFields); 539 540 updates.sort(new SchemaRequestComparator()); 541 542 SchemaRequest.MultiUpdate multiUpdate = new SchemaRequest.MultiUpdate(updates); 543 SchemaResponse.UpdateResponse updateResponse = multiUpdate.process(solrClient, collection); 544 545 getLogger().debug("Send schema response: {}", updateResponse.toString()); 546 Object errors = updateResponse.getResponse().get("errors"); 547 if (errors != null && errors instanceof List && !((List) errors).isEmpty()) 548 { 549 String msg = "An error occured with the sent schema to Solr, it contains errors:\n" + errors.toString(); 550 throw new SolrServerException(msg); 551 } 552 553 getLogger().info("Schema sent to the solr server."); 554 555 reloadCores(); 556 } 557 558 /** 559 * Compute the list of {@link Update} directives from the static schema. 560 * @param updates The list of {@link Update} directives to fill. 561 * @param staticSchema The static schema representation. 562 * @param schemaFields The current schema fields, used to track the existing fields (to be filled). 563 */ 564 protected void addStaticSchemaUpdates(List<SchemaRequest.Update> updates, SchemaRepresentation staticSchema, SchemaFields schemaFields) 565 { 566 List<FieldTypeDefinition> fieldTypes = staticSchema.getFieldTypes(); 567 for (FieldTypeDefinition fieldType : fieldTypes) 568 { 569 String name = (String) fieldType.getAttributes().get("name"); 570 if (!schemaFields.hasFieldType(name)) 571 { 572 updates.add(new SchemaRequest.AddFieldType(fieldType)); 573 schemaFields.addFieldType(name); 574 } 575 } 576 for (Map<String, Object> field : staticSchema.getFields()) 577 { 578 String name = (String) field.get("name"); 579 if (!schemaFields.hasField(name)) 580 { 581 updates.add(new SchemaRequest.AddField(field)); 582 schemaFields.addField(name); 583 } 584 } 585 for (Map<String, Object> field : staticSchema.getDynamicFields()) 586 { 587 String name = (String) field.get("name"); 588 if (!schemaFields.hasDynamicField(name)) 589 { 590 updates.add(new SchemaRequest.AddDynamicField(field)); 591 schemaFields.addDynamicField(name); 592 } 593 } 594 for (Map<String, Object> field : staticSchema.getCopyFields()) 595 { 596 String source = (String) field.get("source"); 597 String dest = (String) field.get("dest"); 598 if (!schemaFields.hasCopyField(source, dest)) 599 { 600 updates.add(new SchemaRequest.AddCopyField(source, Arrays.asList(dest))); 601 schemaFields.addCopyField(source, dest); 602 } 603 } 604 } 605 606 /** 607 * Compute the list of custom {@link Update} directives. 608 * @param updates The list of {@link Update} directives to fill. 609 * @param schemaFields The current schema fields, used to track the existing fields (to be filled). 610 */ 611 protected void addCustomUpdates(List<SchemaRequest.Update> updates, SchemaFields schemaFields) 612 { 613 // Add all our property-managed fields. 614 for (String providerId : _schemaDefProviderEP.getExtensionsIds()) 615 { 616 SchemaDefinitionProvider definitionProvider = _schemaDefProviderEP.getExtension(providerId); 617 618 for (SchemaDefinition definition : definitionProvider.getDefinitions()) 619 { 620 if (!definition.exists(schemaFields)) 621 { 622 SchemaRequest.Update update = definition.getSchemaUpdate(); 623 if (update != null) 624 { 625 updates.add(update); 626 } 627 } 628 } 629 } 630 } 631 632 /** 633 * Delete all the fields of the existing schema in the given collection. 634 * @param solrClient The Solr client 635 * @param collection The collection. 636 * @throws IOException If a communication error occurs. 637 * @throws SolrServerException If a solr error occurs. 638 */ 639 protected void clearSchema(SolrClient solrClient, String collection) throws IOException, SolrServerException 640 { 641 try 642 { 643 getLogger().info("Clearing the existing schema on the solr server."); 644 645 SchemaRequest schemaRequest = new SchemaRequest(); 646 SchemaResponse schemaResponse = schemaRequest.process(solrClient, collection); 647 648 SchemaRepresentation schema = schemaResponse.getSchemaRepresentation(); 649 650 List<SchemaRequest.Update> deletions = new ArrayList<>(); 651 652 // First the copy fields, then dynamic and simple fields, and field types in the end. 653 for (Map<String, Object> field : schema.getCopyFields()) 654 { 655 String source = (String) field.get("source"); 656 String dest = (String) field.get("dest"); 657 deletions.add(new SchemaRequest.DeleteCopyField(source, Arrays.asList(dest))); 658 } 659 for (Map<String, Object> field : schema.getDynamicFields()) 660 { 661 String name = (String) field.get("name"); 662 deletions.add(new SchemaRequest.DeleteDynamicField(name)); 663 } 664 for (Map<String, Object> field : schema.getFields()) 665 { 666 String name = (String) field.get("name"); 667 if (!_READ_ONLY_FIELDS.contains(name)) 668 { 669 deletions.add(new SchemaRequest.DeleteField(name)); 670 } 671 } 672 for (FieldTypeDefinition fieldType : schema.getFieldTypes()) 673 { 674 String name = (String) fieldType.getAttributes().get("name"); 675 if (!_READ_ONLY_FIELDTYPES.contains(name)) 676 { 677 deletions.add(new SchemaRequest.DeleteFieldType(name)); 678 } 679 } 680 681 SchemaRequest.MultiUpdate multiUpdate = new SchemaRequest.MultiUpdate(deletions); 682 SchemaResponse.UpdateResponse updateResponse = multiUpdate.process(solrClient, collection); 683 684 Object errors = updateResponse.getResponse().get("errors"); 685 if (errors != null && errors instanceof List && !((List) errors).isEmpty()) 686 { 687 String msg = "An error occured when clearing Solr schema, it contains errors:\n" + errors.toString(); 688 throw new SolrServerException(msg); 689 } 690 else 691 { 692 getLogger().debug("Clear schema response: {}", updateResponse.toString()); 693 } 694 695 getLogger().info("Solr schema cleared."); 696 } 697 catch (SolrServerException | IOException e) 698 { 699 getLogger().error("Error clearing schema in collection " + collection, e); 700 throw e; 701 } 702 } 703 704 /** 705 * Reload the solr cores. 706 * @throws IOException If a communication error occurs. 707 * @throws SolrServerException If a solr error occurs. 708 */ 709 protected void reloadCores() throws IOException, SolrServerException 710 { 711 getLogger().info("Reloading solr cores."); 712 713 for (String coreName : getCoreNames()) 714 { 715 String fullName = _solrCorePrefix + coreName; 716 717 CoreAdminResponse reloadResponse = CoreAdminRequest.reloadCore(fullName, _defaultSolrClient()); 718 719 getLogger().debug("Reload core response: {}", reloadResponse.toString()); 720 } 721 722 getLogger().info("All cores reloaded."); 723 } 724 725 /** 726 * Reloads the ACL Solr cache for all users 727 * @throws IOException If an I/O error occurs. 728 * @throws SolrServerException If a Solr error occurs. 729 * @throws RepositoryException If a repository exception occurs when retrieving workspaces. 730 */ 731 public void reloadAclCache() throws IOException, SolrServerException, RepositoryException 732 { 733 String[] workspaceNames = _repository.getWorkspaces(); 734 for (String workspaceName : workspaceNames) 735 { 736 reloadAclCache(workspaceName); 737 } 738 } 739 740 /** 741 * Reloads the ACL Solr cache for all users 742 * @param workspaceName The workspace name 743 * @throws IOException If an I/O error occurs. 744 * @throws SolrServerException If a Solr error occurs. 745 */ 746 public void reloadAclCache(String workspaceName) throws IOException, SolrServerException 747 { 748 reloadAclCache(workspaceName, false); 749 } 750 751 /** 752 * Reloads the ACL Solr cache for all users 753 * @param workspaceName The workspace name 754 * @param checkIfNecessary true to check if the reload is necessary for each segment (i.e. reload only the segments not already in cache) 755 * @throws IOException If an I/O error occurs. 756 * @throws SolrServerException If a Solr error occurs. 757 */ 758 public void reloadAclCache(String workspaceName, boolean checkIfNecessary) throws IOException, SolrServerException 759 { 760 getLogger().info("Reloading read ACL Solr cache for workspace '{}' (checkIfNecessary={})", workspaceName, checkIfNecessary); 761 762 String collection = _solrClientProvider.getCollectionName(workspaceName); 763 SolrResponseBase responseBase = new ReloadAclCacheRequest(checkIfNecessary).process(_getAutoCommitSolrClient(workspaceName), collection); 764 NamedList<Object> responseObj = responseBase.getResponse(); 765 766 if ("ok".equals(responseObj.get("result"))) 767 { 768 getLogger().info("Read-ACL Solr cache reloaded for workspace '{}' (checkIfNecessary={})", workspaceName, checkIfNecessary); 769 } 770 else 771 { 772 Object error = responseObj.get("error"); 773 getLogger().error("The reloading of Read-ACL Solr Cache for workspace '{}' (checkIfNecessary={}) did not succeed as expected.\n Error code is the following: {}", workspaceName, checkIfNecessary, error); 774 } 775 } 776 777 /** 778 * Updates the ACL Solr cache for some {@link AmetysObject}s for all workspaces. 779 * @param objects the {@link AmetysObject}s to update. 780 * @throws IOException If an I/O error occurs. 781 * @throws SolrServerException If a Solr error occurs. 782 * @throws RepositoryException If a repository exception occurs when retrieving workspaces. 783 */ 784 public void updateAclCache(Iterable<? extends AmetysObject> objects) throws IOException, SolrServerException, RepositoryException 785 { 786 String[] workspaceNames = _repository.getWorkspaces(); 787 for (String workspaceName : workspaceNames) 788 { 789 updateAclCache(objects, workspaceName); 790 } 791 } 792 793 /** 794 * Updates the ACL Solr cache for some {@link AmetysObject}s. 795 * @param objects the {@link AmetysObject}s to update. 796 * @param workspaceName The workspace name 797 * @throws IOException If an I/O error occurs. 798 * @throws SolrServerException If a Solr error occurs. 799 * @throws RepositoryException If a repository exception occurs when retrieving workspaces. 800 */ 801 public void updateAclCache(Iterable<? extends AmetysObject> objects, String workspaceName) throws IOException, SolrServerException, RepositoryException 802 { 803 Map<String, Map<String, Object>> solrParams = new HashMap<>(); 804 805 for (AmetysObject object : objects) 806 { 807 AllowedUsers allowedUsers = _readAccessHelper.allowedUsers(object); 808 809 solrParams.put(object.getId(), Map.of("anonymous", allowedUsers.isAnonymousAllowed(), 810 "anyConnectedUser", allowedUsers.isAnyConnectedUserAllowed(), 811 "allowedUsers", allowedUsers.getAllowedUsers().stream().map(UserIdentity::userIdentityToString).collect(Collectors.toList()), 812 "deniedUsers", allowedUsers.getDeniedUsers().stream().map(UserIdentity::userIdentityToString).collect(Collectors.toList()), 813 "allowedGroups", allowedUsers.getAllowedGroups().stream().map(GroupIdentity::groupIdentityToString).collect(Collectors.toList()), 814 "deniedGroups", allowedUsers.getDeniedGroups().stream().map(GroupIdentity::groupIdentityToString).collect(Collectors.toList()))); 815 } 816 817 String collection = _solrClientProvider.getCollectionName(workspaceName); 818 SolrResponse response = new UpdateAclCacheRequest(solrParams).process(_getAutoCommitSolrClient(workspaceName), collection); 819 NamedList<Object> responseObj = response.getResponse(); 820 821 if ("ok".equals(responseObj.get("result"))) 822 { 823 getLogger().info("Read-ACL Solr cache updated for workspace '{}' and objects {}", workspaceName, solrParams.keySet()); 824 } 825 else 826 { 827 @SuppressWarnings("unchecked") 828 List<String> unHandledObjects = (List<String>) responseObj.get("unhandled-objects"); 829 getLogger().info("The updating of Read-ACL Solr Cache for workspace '{}' did not succeed as expected.\n Following objects have not been updated: {}", workspaceName, unHandledObjects); 830 } 831 } 832 833 /** 834 * Index all the contents in a given workspace. 835 * @param workspaceName the workspace where to index 836 * @param indexAttachments to index content attachments 837 * @param solrClient The solr client to use 838 * @return The indexation result as a Map. 839 * @throws Exception if an error occurs while indexing. 840 */ 841 public Map<String, Object> indexAllContents(String workspaceName, boolean indexAttachments, SolrClient solrClient) throws Exception 842 { 843 return indexAllContents(workspaceName, indexAttachments, solrClient, ProgressionTrackerFactory.createSimpleProgressionTracker("Index all contents", getLogger())); 844 } 845 846 /** 847 * Index all the contents in a given workspace. 848 * @param workspaceName the workspace where to index 849 * @param indexAttachments to index content attachments 850 * @param solrClient The solr client to use 851 * @param progressionTracker The progression of the indexation 852 * @return The indexation result as a Map. 853 * @throws Exception if an error occurs while indexing. 854 */ 855 public Map<String, Object> indexAllContents(String workspaceName, boolean indexAttachments, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 856 { 857 Request request = ContextHelper.getRequest(_context); 858 859 // Retrieve the current workspace. 860 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 861 862 try 863 { 864 // Force the workspace. 865 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 866 867 getLogger().info("Starting the indexation of all contents for workspace {}", workspaceName); 868 869 long start = System.currentTimeMillis(); 870 871 // Delete all contents 872 unindexAllContents(workspaceName, indexAttachments, solrClient); 873 874 String query = ContentQueryHelper.getContentXPathQuery(null); 875 AmetysObjectIterable<Content> contents = _resolver.query(query); 876 877 IndexationResult result = doIndexContents(contents, workspaceName, indexAttachments, solrClient, progressionTracker); 878 879 long end = System.currentTimeMillis(); 880 881 if (!result.hasErrors()) 882 { 883 getLogger().info("{} contents indexed without error in {} milliseconds.", result.successCount(), end - start); 884 } 885 else 886 { 887 getLogger().info("Content indexation ended, the process took {} milliseconds. {} contents were not indexed successfully, please review the error logs above for more details.", end - start, result.errorCount()); 888 } 889 890 Map<String, Object> results = new HashMap<>(); 891 results.put("successCount", result.successCount()); 892 if (result.hasErrors()) 893 { 894 results.put("errorCount", result.errorCount()); 895 } 896 897 return results; 898 } 899 catch (Exception e) 900 { 901 String error = String.format("Failed to index all contents in workspace %s", workspaceName); 902 getLogger().error(error, e); 903 throw new IndexingException(error, e); 904 } 905 finally 906 { 907 // Restore context 908 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 909 } 910 } 911 912 /** 913 * Unindex all content documents. 914 * @param workspaceName The workspace name 915 * @param unindexAttachments also unindex content attachments 916 * @param solrClient The solr client to use 917 * @throws Exception if an error occurs while unindexing. 918 */ 919 protected void unindexAllContents(String workspaceName, boolean unindexAttachments, SolrClient solrClient) throws Exception 920 { 921 String collection = _solrClientProvider.getCollectionName(workspaceName); 922 923 Query contents = new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT); 924 Query query; 925 if (unindexAttachments) 926 { 927 Query contentResourceAttachments = new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT_ATTACHMENT_RESOURCE); 928 Query contentResourceAttributes = new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT_ATTRIBUTE_RESOURCE); 929 query = new OrQuery(contentResourceAttachments, contentResourceAttributes, contents); 930 } 931 else 932 { 933 query = contents; 934 } 935 936 Query additionalQuery = unindexAllAdditionnalData(AdditionalDataIndexer.TYPE_CONTENT); 937 if (additionalQuery != null) 938 { 939 query = new OrQuery(query, additionalQuery); 940 } 941 942 solrClient.deleteByQuery(collection, query.build()); 943 } 944 945 /** 946 * Add or update a content into Solr index on all workspaces and commit 947 * @param contentId The id of the content to index 948 * @param indexAttachments to index content attachments 949 * @throws Exception if an error occurs while indexing. 950 */ 951 public void indexContent(String contentId, boolean indexAttachments) throws Exception 952 { 953 String[] workspaceNames = _repository.getWorkspaces(); 954 for (String workspaceName : workspaceNames) 955 { 956 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 957 indexContent(contentId, workspaceName, indexAttachments, solrClient); 958 } 959 } 960 961 /** 962 * Add or update a content into Solr index 963 * @param contentId The id of the content to index 964 * @param workspaceName the workspace where to index 965 * @param indexAttachments to index content attachments 966 * @throws Exception if an error occurs while indexing. 967 */ 968 public void indexContent(String contentId, String workspaceName, boolean indexAttachments) throws Exception 969 { 970 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 971 indexContent(contentId, workspaceName, indexAttachments, solrClient); 972 } 973 974 /** 975 * Add or update a content into Solr index 976 * @param contentId The id of the content to index 977 * @param workspaceName the workspace where to index 978 * @param indexAttachments to index content attachments 979 * @param solrClient The solr client to use 980 * @throws Exception if an error occurs while indexing. 981 */ 982 public void indexContent(String contentId, String workspaceName, boolean indexAttachments, SolrClient solrClient) throws Exception 983 { 984 Request request = ContextHelper.getRequest(_context); 985 986 // Retrieve the current workspace. 987 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 988 989 try 990 { 991 // Force the workspace. 992 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 993 994 if (_resolver.hasAmetysObjectForId(contentId)) 995 { 996 Content content = _resolver.resolveById(contentId); 997 _doIndexContent(content, workspaceName, indexAttachments, solrClient); 998 } 999 } 1000 finally 1001 { 1002 // Restore context 1003 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1004 } 1005 } 1006 1007 private void _doIndexContent(Content content, String workspaceName, boolean indexAttachments, SolrClient solrClient) throws IndexingException 1008 { 1009 try 1010 { 1011 long time_0 = System.currentTimeMillis(); 1012 1013 getLogger().debug("Indexing content {} into Solr for workspace {}", content.getId(), workspaceName); 1014 1015 deleteRepeaterDocs(content.getId(), workspaceName, solrClient); 1016 doIndexContent(content, workspaceName, solrClient); 1017 doIndexContentWorkflow(content, workspaceName, solrClient); 1018 if (indexAttachments) 1019 { 1020 indexContentAttachments(content.getRootAttachments(), content, solrClient); 1021 } 1022 1023 getLogger().debug("Successfully indexed content {} in Solr in {} ms", content.getId(), System.currentTimeMillis() - time_0); 1024 } 1025 catch (Exception e) 1026 { 1027 String error = String.format("Failed to index content %s in workspace %s", content.getId(), workspaceName); 1028 getLogger().error(error, e); 1029 throw new IndexingException(error, e); 1030 } 1031 } 1032 1033 /** 1034 * Send a collection of contents for indexation in the solr server on all workspaces and commit 1035 * @param contents the collection of contents to index. 1036 * @return the indexation result. 1037 * @throws Exception if an error occurs while indexing. 1038 */ 1039 public IndexationResult indexContents(Iterable<Content> contents) throws Exception 1040 { 1041 return indexContents(contents, ProgressionTrackerFactory.createContainerProgressionTracker("Index contents", getLogger())); 1042 } 1043 1044 1045 /** 1046 * Send a collection of contents for indexation in the solr server on all workspaces and commit 1047 * @param contents the collection of contents to index. 1048 * @param progressionTracker The progression of the indexation 1049 * @return the indexation result. 1050 * @throws Exception if an error occurs while indexing. 1051 */ 1052 public IndexationResult indexContents(Iterable<Content> contents, ContainerProgressionTracker progressionTracker) throws Exception 1053 { 1054 IndexationResult result = new IndexationResult(0, 0); 1055 1056 String[] workspaceNames = _repository.getWorkspaces(); 1057 1058 for (String workspaceName : workspaceNames) 1059 { 1060 progressionTracker.addSimpleStep(workspaceName, new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULER_GLOBAL_INDEXATION_CONTENT_WORKSPACE_STEP_LABEL", List.of(workspaceName))); 1061 } 1062 1063 for (String workspaceName : workspaceNames) 1064 { 1065 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1066 IndexationResult wResult = indexContents(contents, workspaceName, true, solrClient, progressionTracker.getStep(workspaceName)); 1067 1068 result = new IndexationResult(result.successCount() + wResult.successCount(), result.errorCount() + wResult.errorCount()); 1069 } 1070 1071 return result; 1072 } 1073 1074 /** 1075 * Send a collection of contents for indexation in the solr server. 1076 * @param contents the collection of contents to index. 1077 * @param workspaceName the workspace where to index 1078 * @param indexAttachments to index content attachments 1079 * @param solrClient The solr client to use 1080 * @return the indexation result. 1081 * @throws Exception if an error occurs while indexing. 1082 */ 1083 public IndexationResult indexContents(Iterable<Content> contents, String workspaceName, boolean indexAttachments, SolrClient solrClient) throws Exception 1084 { 1085 return indexContents(contents, workspaceName, indexAttachments, solrClient, ProgressionTrackerFactory.createSimpleProgressionTracker("Index contents for workspace " + workspaceName, getLogger())); 1086 } 1087 1088 /** 1089 * Send a collection of contents for indexation in the solr server. 1090 * @param contents the collection of contents to index. 1091 * @param workspaceName the workspace where to index 1092 * @param indexAttachments to index content attachments 1093 * @param solrClient The solr client to use 1094 * @param progressionTracker The progression of the indexation 1095 * @return the indexation result. 1096 * @throws Exception if an error occurs while indexing. 1097 */ 1098 public IndexationResult indexContents(Iterable<Content> contents, String workspaceName, boolean indexAttachments, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 1099 { 1100 Request request = ContextHelper.getRequest(_context); 1101 1102 // Retrieve the current workspace. 1103 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1104 1105 try 1106 { 1107 // Force the workspace. 1108 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 1109 1110 getLogger().info("Starting indexation of several contents for workspace {}", workspaceName); 1111 1112 long start = System.currentTimeMillis(); 1113 1114 List<Content> contentsInWorkspace = StreamSupport.stream(contents.spliterator(), false) 1115 .map(Content::getId) 1116 .map(this::_resolveSilently) 1117 .filter(Objects::nonNull) 1118 .collect(Collectors.toList()); 1119 1120 IndexationResult result = doIndexContents(contentsInWorkspace, workspaceName, indexAttachments, solrClient, progressionTracker); 1121 1122 long end = System.currentTimeMillis(); 1123 1124 if (!result.hasErrors()) 1125 { 1126 getLogger().info("{} contents indexed without error in {} milliseconds.", result.successCount(), end - start); 1127 } 1128 else 1129 { 1130 getLogger().info("Content indexation ended, the process took {} milliseconds. {} contents were not indexed successfully, please review the error logs above for more details.", end - start, result.errorCount()); 1131 } 1132 1133 return result; 1134 } 1135 catch (Exception e) 1136 { 1137 String error = String.format("Failed to index several contents in workspace %s", workspaceName); 1138 getLogger().error(error, e); 1139 throw new IndexingException(error, e); 1140 } 1141 finally 1142 { 1143 // Restore context 1144 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1145 } 1146 } 1147 1148 private Content _resolveSilently(String contentId) 1149 { 1150 try 1151 { 1152 return _resolver.resolveById(contentId); 1153 } 1154 catch (UnknownAmetysObjectException e) 1155 { 1156 return null; 1157 } 1158 } 1159 1160 /** 1161 * Send some contents for indexation in the solr server. 1162 * @param contents the contents to index. 1163 * @param workspaceName The workspace name 1164 * @param indexAttachments to index content attachments 1165 * @param solrClient The solr client to use 1166 * @param progressionTracker The progression of the indexation 1167 * @return the indexation result. 1168 * @throws Exception if an error occurs committing the results. 1169 */ 1170 protected IndexationResult doIndexContents(Iterable<Content> contents, String workspaceName, boolean indexAttachments, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 1171 { 1172 int numberOfContents = IterableUtils.size(contents); 1173 progressionTracker.setSize(numberOfContents); 1174 1175 // Add callable for each content to index 1176 List<Future<Void>> tasks = new ArrayList<>(); 1177 for (Content content : contents) 1178 { 1179 tasks.add(_threadIndexerHelper.submitCallable(new ContentIndexerCallable(content, workspaceName, indexAttachments, solrClient, progressionTracker))); 1180 } 1181 1182 // Now that everything is submitted, we can iterate and wait for result 1183 return IndexationResult.fromTasks(tasks, getLogger()); 1184 } 1185 1186 /** 1187 * Update the value of a specific system property in a ametys object document. 1188 * @param ao The ametys object to update. 1189 * @param propertyId The system property ID. 1190 * @param workspaceName The workspace name 1191 * @throws Exception if an error occurs while indexing. 1192 */ 1193 public void updateSystemProperty(IndexableAmetysObject ao, String propertyId, String workspaceName) throws Exception 1194 { 1195 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1196 updateSystemProperty(ao, propertyId, workspaceName, solrClient); 1197 } 1198 1199 /** 1200 * Update the value of a specific system property in a ametys object document. 1201 * @param ao The ametys object to update. 1202 * @param propertyId The system property ID. 1203 * @param workspaceName The workspace name 1204 * @param solrClient The solr client to use 1205 * @throws Exception if an error occurs while indexing. 1206 */ 1207 public void updateSystemProperty(IndexableAmetysObject ao, String propertyId, String workspaceName, SolrClient solrClient) throws Exception 1208 { 1209 getLogger().debug("Updating the system property '{}' for ametys object {} into Solr.", propertyId, ao); 1210 1211 SolrInputDocument document = new SolrInputDocument(); 1212 boolean hasUpdate = populatePartialSystemProperty(ao, propertyId, document); 1213 1214 if (!hasUpdate) 1215 { 1216 getLogger().debug("Did not index '{}' system property for ametys object {} in Solr because no update to apply.", propertyId, ao); 1217 return; 1218 } 1219 1220 int status = _pushSolrDocument(document, workspaceName, solrClient); 1221 if (status != 0) 1222 { 1223 throw new IOException("Indexing of system property '" + propertyId + "': got status code '" + status + "'."); 1224 } 1225 1226 getLogger().debug("Succesfully indexed '{}' system property for ametys object {} in Solr.", propertyId, ao); 1227 } 1228 1229 /** 1230 * Populate a Solr input document by adding fields for a single system property. 1231 * @param ao The ametys object to index 1232 * @param propertyId The system property ID. 1233 * @param document The solr document 1234 * @return true if there are partial update to apply 1235 * @throws Exception if an error occurred 1236 */ 1237 public boolean populatePartialSystemProperty(IndexableAmetysObject ao, String propertyId, SolrInputDocument document) throws Exception 1238 { 1239 Optional<SystemProperty> property = ao.getSystemPropertyExtensionPoint() 1240 .filter(s -> s.hasExtension(propertyId)) 1241 .map(s -> s.getExtension(propertyId)); 1242 1243 if (property.isEmpty()) 1244 { 1245 throw new IllegalStateException("The system property '" + propertyId + "' can't be indexed for ametys object with id '" + ao.getId() + "' as it does not exist."); 1246 } 1247 1248 return populatePartialProperty(ao, property.get(), document); 1249 } 1250 1251 /** 1252 * Populate a Solr input document by adding fields for a single property. 1253 * @param ao The ametys object to index 1254 * @param propertyId The property ID. 1255 * @param document The solr document 1256 * @return true if there are partial update to apply 1257 * @throws Exception if an error occurred 1258 */ 1259 public boolean populatePartialProperty(IndexableAmetysObject ao, String propertyId, SolrInputDocument document) throws Exception 1260 { 1261 ModelItem modelItem = ao.getDefinition(propertyId); 1262 if (modelItem instanceof Property property) 1263 { 1264 return populatePartialProperty(ao, property, document); 1265 } 1266 1267 return false; 1268 } 1269 1270 /** 1271 * Populate a Solr input document by adding fields for a single property. 1272 * @param ao The ametys object to index 1273 * @param property The property to index. 1274 * @param document The solr document 1275 * @return true if there are partial update to apply 1276 * @throws Exception if an error occurred 1277 */ 1278 @SuppressWarnings("unchecked") 1279 public boolean populatePartialProperty(IndexableAmetysObject ao, Property property, SolrInputDocument document) throws Exception 1280 { 1281 SolrInputDocument tempDocument = new SolrInputDocument(); 1282 1283 if (property instanceof IndexationAwareElementDefinition indexationAwareElementDefinition) 1284 { 1285 indexationAwareElementDefinition.indexValue(tempDocument, ao, CMSDataContext.newInstance()); 1286 } 1287 1288 if (tempDocument.isEmpty()) 1289 { 1290 // Does not have any partial update to apply, avoid to erase all the existing fields on the Solr document corresponding to this content (it would be lost) 1291 return false; 1292 } 1293 1294 // Copy the indexed values as partial updates. 1295 for (String fieldName : tempDocument.getFieldNames()) 1296 { 1297 Collection<Object> fieldValues = tempDocument.getFieldValues(fieldName); 1298 1299 Map<String, Object> partialUpdate = new HashMap<>(); 1300 partialUpdate.put("set", fieldValues); 1301 document.addField(fieldName, partialUpdate); 1302 } 1303 1304 document.addField("id", ao.getId()); 1305 1306 return true; 1307 } 1308 1309 /** 1310 * Update the value of a specific property in a content document. 1311 * @param content The content to update. 1312 * @param propertyId The system property ID. 1313 * @param workspaceName The workspace name 1314 * @throws Exception if an error occurs while indexing. 1315 */ 1316 public void updateProperty(Content content, String propertyId, String workspaceName) throws Exception 1317 { 1318 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1319 updateProperty(content, propertyId, workspaceName, solrClient); 1320 } 1321 1322 /** 1323 * Update the value of a specific property in a content document. 1324 * @param content The content to update. 1325 * @param propertyId The system property ID. 1326 * @param workspaceName The workspace name 1327 * @param solrClient The solr client to use 1328 * @throws Exception if an error occurs while indexing. 1329 */ 1330 public void updateProperty(Content content, String propertyId, String workspaceName, SolrClient solrClient) throws Exception 1331 { 1332 getLogger().debug("Updating the property '{}' for content {} into Solr.", propertyId, content); 1333 1334 SolrInputDocument document = new SolrInputDocument(); 1335 boolean hasUpdate = populatePartialProperty(content, propertyId, document); 1336 1337 if (!hasUpdate) 1338 { 1339 getLogger().debug("Did not index '{}' property for content {} in Solr because no update to apply.", propertyId, content); 1340 return; 1341 } 1342 1343 int status = _pushSolrDocument(document, workspaceName, solrClient); 1344 if (status != 0) 1345 { 1346 throw new IOException("Indexing of property '" + propertyId + "': got status code '" + status + "'."); 1347 } 1348 1349 getLogger().debug("Succesfully indexed '{}' property for content {} in Solr.", propertyId, content); 1350 } 1351 1352 private int _pushSolrDocument(SolrInputDocument document, String workspaceName, SolrClient solrClient) throws Exception 1353 { 1354 String collection = _solrClientProvider.getCollectionName(workspaceName); 1355 UpdateResponse solrResponse = solrClient.add(collection, document); 1356 return solrResponse.getStatus(); 1357 } 1358 1359 /** 1360 * Remove a content from Solr index for all workspaces and commit 1361 * @param contentId The id of content to unindex 1362 * @param unindexAttachments also unindex content attachments 1363 * @throws Exception if an error occurs while indexing. 1364 */ 1365 public void unindexContent(String contentId, boolean unindexAttachments) throws Exception 1366 { 1367 String[] workspaceNames = _repository.getWorkspaces(); 1368 for (String workspaceName : workspaceNames) 1369 { 1370 unindexContent(contentId, workspaceName, unindexAttachments); 1371 } 1372 } 1373 1374 /** 1375 * Remove a content from Solr index 1376 * @param contentId The id of content to unindex 1377 * @param workspaceName The workspace where to work in 1378 * @param unindexAttachments also unindex content attachments 1379 * @throws Exception if an error occurs while indexing. 1380 */ 1381 public void unindexContent(String contentId, String workspaceName, boolean unindexAttachments) throws Exception 1382 { 1383 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1384 unindexContent(contentId, workspaceName, unindexAttachments, solrClient); 1385 } 1386 1387 /** 1388 * Remove a content from Solr index 1389 * @param contentId The id of content to unindex 1390 * @param workspaceName The workspace where to work in 1391 * @param unindexAttachments also unindex content attachments 1392 * @param solrClient The solr client to use 1393 * @throws Exception if an error occurs while indexing. 1394 */ 1395 public void unindexContent(String contentId, String workspaceName, boolean unindexAttachments, SolrClient solrClient) throws Exception 1396 { 1397 Request request = ContextHelper.getRequest(_context); 1398 1399 // Retrieve the current workspace. 1400 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1401 1402 try 1403 { 1404 // Force the workspace. 1405 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 1406 1407 getLogger().debug("Unindexing content {} from Solr for workspace {}", contentId, workspaceName); 1408 1409 deleteRepeaterDocs(contentId, workspaceName, solrClient); 1410 doUnindexDocument(contentId, workspaceName, solrClient); 1411 if (unindexAttachments) 1412 { 1413 doUnindexContentAttachments(contentId, workspaceName, solrClient); 1414 } 1415 _solrWorkflowIndexer.unindexAmetysObjectWorkflow(contentId, workspaceName, solrClient); 1416 unindexAdditionnalData("content", contentId, workspaceName, solrClient); 1417 1418 getLogger().debug("Succesfully deleted content {} from Solr.", contentId); 1419 } 1420 catch (Exception e) 1421 { 1422 String error = String.format("Failed to unindex content %s in workspace %s", contentId, workspaceName); 1423 getLogger().error(error, e); 1424 throw new IndexingException(error, e); 1425 } 1426 finally 1427 { 1428 // Restore workspace 1429 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1430 } 1431 } 1432 1433 /** 1434 * Remove a content from Solr index for all workspaces and commit 1435 * @param contentIds The id of content to unindex 1436 * @throws Exception if an error occurs while indexing. 1437 */ 1438 public void unindexContents(Collection<String> contentIds) throws Exception 1439 { 1440 String[] workspaceNames = _repository.getWorkspaces(); 1441 for (String workspaceName : workspaceNames) 1442 { 1443 unindexContents(contentIds, workspaceName); 1444 } 1445 } 1446 1447 /** 1448 * Remove a content from Solr index 1449 * @param contentIds The id of content to unindex 1450 * @param workspaceName The workspace where to work in 1451 * @throws Exception if an error occurs while indexing. 1452 */ 1453 public void unindexContents(Collection<String> contentIds, String workspaceName) throws Exception 1454 { 1455 Request request = ContextHelper.getRequest(_context); 1456 1457 // Retrieve the current workspace. 1458 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1459 1460 try 1461 { 1462 // Force the workspace. 1463 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 1464 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1465 1466 getLogger().debug("Unindexing several contents from Solr."); 1467 1468 for (String contentId : contentIds) 1469 { 1470 deleteRepeaterDocs(contentId, workspaceName, solrClient); 1471 doUnindexDocument(contentId, workspaceName, solrClient); 1472 _solrWorkflowIndexer.unindexAmetysObjectWorkflow(contentId, workspaceName, solrClient); 1473 unindexAdditionnalData("content", contentId, workspaceName, solrClient); 1474 } 1475 1476 getLogger().debug("Succesfully unindexed content from Solr."); 1477 } 1478 catch (Exception e) 1479 { 1480 String error = String.format("Failed to unindex several contents in workspace %s", workspaceName); 1481 getLogger().error(error, e); 1482 throw new IndexingException(error, e); 1483 } 1484 finally 1485 { 1486 // Restore workspace 1487 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1488 } 1489 } 1490 1491 /** 1492 * Add or update a content into Solr index 1493 * @param content The content to index 1494 * @param workspaceName The workspace where to index 1495 * @param solrClient The solr client to use 1496 * @throws Exception if an error occurs while indexing. 1497 */ 1498 protected void doIndexContent(Content content, String workspaceName, SolrClient solrClient) throws Exception 1499 { 1500 long time_0 = System.currentTimeMillis(); 1501 1502 SolrInputDocument document = new SolrInputDocument(); 1503 List<SolrInputDocument> additionalDocuments = _solrContentIndexer.indexContent(content, document); 1504 1505 long time_1 = System.currentTimeMillis(); 1506 getLogger().debug("Populate indexing fields for content {} for workspace {} in {} ms", content.getId(), workspaceName, time_1 - time_0); 1507 1508 indexAclInitValues(content, document); 1509 1510 long time_2 = System.currentTimeMillis(); 1511 getLogger().debug("Populate ACL for content {} for workspace {} in {} ms", content.getId(), workspaceName, time_2 - time_1); 1512 1513 List<SolrInputDocument> documents = new ArrayList<>(additionalDocuments); 1514 documents.add(document); 1515 1516 UpdateResponse solrResponse = solrClient.add(_solrClientProvider.getCollectionName(workspaceName), documents); 1517 int status = solrResponse.getStatus(); 1518 1519 long time_3 = System.currentTimeMillis(); 1520 getLogger().debug("Update document for content {} for workspace {} in {} ms", content.getId(), workspaceName, time_3 - time_2); 1521 1522 if (status != 0) 1523 { 1524 throw new IOException("Content indexation: got status code '" + status + "'."); 1525 } 1526 } 1527 1528 /** 1529 * Indexes read-ACl initial values for object 1530 * @param ametysObject The object 1531 * @param document The Solr document 1532 */ 1533 public void indexAclInitValues(AmetysObject ametysObject, SolrInputDocument document) 1534 { 1535 // Indexation of AmetysObject property 1536 document.addField(SolrFieldNames.IS_AMETYS_OBJECT, true); 1537 1538 // Indexation of initValues for AllowedUsers 1539 AllowedUsers allowedUsers = _readAccessHelper.allowedUsers(ametysObject); 1540 document.addField(SolrFieldNames.ACL_INIT_VALUE_ANONYMOUS, allowedUsers.isAnonymousAllowed()); 1541 document.addField(SolrFieldNames.ACL_INIT_VALUE_ANYCONNECTED, allowedUsers.isAnyConnectedUserAllowed()); 1542 _addField(document, SolrFieldNames.ACL_INIT_VALUE_ALLOWED_USERS, allowedUsers.getAllowedUsers(), UserIdentity::userIdentityToString); 1543 _addField(document, SolrFieldNames.ACL_INIT_VALUE_DENIED_USERS, allowedUsers.getDeniedUsers(), UserIdentity::userIdentityToString); 1544 _addField(document, SolrFieldNames.ACL_INIT_VALUE_ALLOWED_GROUPS, allowedUsers.getAllowedGroups(), GroupIdentity::groupIdentityToString); 1545 _addField(document, SolrFieldNames.ACL_INIT_VALUE_DENIED_GROUPS, allowedUsers.getDeniedGroups(), GroupIdentity::groupIdentityToString); 1546 } 1547 1548 private <T> void _addField(SolrInputDocument document, String fieldName, Set<T> values, Function<T, String> stringifier) 1549 { 1550 if (values == null) 1551 { 1552 return; 1553 } 1554 1555 for (T value : values) 1556 { 1557 document.addField(fieldName, stringifier.apply(value)); 1558 } 1559 } 1560 1561 /** 1562 * Index the whole workflow of a content. 1563 * @param content The content. 1564 * @param workspaceName The workspace name 1565 * @param solrClient The solr client to use 1566 * @throws Exception if an error occurs while indexing. 1567 */ 1568 protected void doIndexContentWorkflow(Content content, String workspaceName, SolrClient solrClient) throws Exception 1569 { 1570 if (content instanceof WorkflowAwareContent) 1571 { 1572 _solrWorkflowIndexer.indexAmetysObjectWorkflow((WorkflowAwareContent) content, workspaceName, solrClient); 1573 } 1574 } 1575 1576 /** 1577 * Index content attachments as new entries in the idnex 1578 * @param collection the collection of attachments 1579 * @param content the content whose attachments will be indexed 1580 * @throws Exception if something goes wrong when indexing the attachments of the content 1581 */ 1582 public void indexContentAttachments(ResourceCollection collection, Content content) throws Exception 1583 { 1584 Request request = ContextHelper.getRequest(_context); 1585 String workspaceName = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1586 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1587 indexContentAttachments(collection, content, solrClient); 1588 } 1589 1590 /** 1591 * Index content attachments as new entries in the idnex 1592 * @param collection the collection of attachments 1593 * @param content the content whose attachments will be indexed 1594 * @param solrClient The solr client to use 1595 * @throws Exception if something goes wrong when indexing the attachments of the content 1596 */ 1597 public void indexContentAttachments(ResourceCollection collection, Content content, SolrClient solrClient) throws Exception 1598 { 1599 if (collection == null) 1600 { 1601 return; 1602 } 1603 1604 try (AmetysObjectIterable<AmetysObject> children = collection.getChildren()) 1605 { 1606 for (AmetysObject object : children) 1607 { 1608 if (object instanceof ResourceCollection) 1609 { 1610 indexContentAttachments((ResourceCollection) object, content, solrClient); 1611 } 1612 else if (object instanceof Resource) 1613 { 1614 Resource resource = (Resource) object; 1615 indexContentAttachment(resource, content, solrClient); 1616 } 1617 } 1618 } 1619 } 1620 1621 /** 1622 * Index a content attachment 1623 * @param resource the content attachment as a {@link Resource} 1624 * @param content the content whose attachment is going to be indexed 1625 * @throws Exception if something goes wrong when processing the indexation of the content attachment 1626 */ 1627 public void indexContentAttachment(Resource resource, Content content) throws Exception 1628 { 1629 Request request = ContextHelper.getRequest(_context); 1630 String workspaceName = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1631 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1632 indexContentAttachment(resource, content, solrClient); 1633 } 1634 1635 /** 1636 * Index a content attachment 1637 * @param resource the content attachment as a {@link Resource} 1638 * @param content the content whose attachment is going to be indexed 1639 * @param solrClient The solr client to use 1640 * @throws Exception if something goes wrong when processing the indexation of the content attachment 1641 */ 1642 public void indexContentAttachment(Resource resource, Content content, SolrClient solrClient) throws Exception 1643 { 1644 SolrInputDocument document = new SolrInputDocument(); 1645 1646 // Prepare resource doc 1647 List<SolrInputDocument> additionalDocuments = _indexContentAttachment(resource, document, content); 1648 List<SolrInputDocument> documents = new ArrayList<>(additionalDocuments); 1649 documents.add(document); 1650 1651 // Indexation of the document 1652 _indexResourceDocument(resource, documents, solrClient); 1653 } 1654 1655 private List<SolrInputDocument> _indexContentAttachment(Resource resource, SolrInputDocument document, Content content) throws Exception 1656 { 1657 String language = content.getLanguage(); 1658 1659 List<SolrInputDocument> additionalDocuments = _solrResourceIndexer.indexResource(resource, document, SolrFieldNames.TYPE_CONTENT_ATTACHMENT_RESOURCE, language); 1660 1661 // Need the id of the content for unindexing attachment during the unindexing of the content 1662 document.addField(SolrFieldNames.ATTACHMENT_CONTENT_ID, content.getId()); 1663 1664 return additionalDocuments; 1665 } 1666 1667 /** 1668 * Index a populated solr input document of type Resource. 1669 * @param resource the resource from which the input document is created 1670 * @param documents the input documents 1671 * @param solrClient The solr client to use 1672 * @throws SolrServerException if there is an error on the server 1673 * @throws IOException if there is a communication error with the server 1674 */ 1675 protected void _indexResourceDocument(Resource resource, List<SolrInputDocument> documents, SolrClient solrClient) throws SolrServerException, IOException 1676 { 1677 String resourceId = resource.getId(); 1678 1679 // Retrieve appropriate collection name 1680 Request request = ContextHelper.getRequest(_context); 1681 String workspaceName = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1682 String collectionName = _solrClientProvider.getCollectionName(workspaceName); 1683 1684 // Add document 1685 UpdateResponse solrResponse = solrClient.add(collectionName, documents); 1686 int status = solrResponse.getStatus(); 1687 1688 if (status != 0) 1689 { 1690 throw new IOException("Ametys resource indexing - Expecting status code of '0' in the Solr response but got : '" + status + "'. Resource id : " + resourceId); 1691 } 1692 1693 getLogger().debug("Successful resource indexing. Resource identifier : {}", resourceId); 1694 } 1695 1696 /** 1697 * Delete repeater documents of a specified content. 1698 * @param workspaceName The workspace name 1699 * @param contentId the content ID. 1700 * @param solrClient The solr client to use 1701 * @throws Exception if an error occurs while indexing. 1702 */ 1703 protected void deleteRepeaterDocs(String contentId, String workspaceName, SolrClient solrClient) throws Exception 1704 { 1705 long time_0 = System.currentTimeMillis(); 1706 1707 // _documentType:repeater AND id:content\://xxx/* 1708 StringBuilder query = new StringBuilder(); 1709 query.append(SolrFieldNames.DOCUMENT_TYPE).append(':').append(SolrFieldNames.TYPE_REPEATER) 1710 .append(" AND id:").append(ClientUtils.escapeQueryChars(contentId)).append("/*"); 1711 1712 solrClient.deleteByQuery(_solrClientProvider.getCollectionName(workspaceName), query.toString()); 1713 1714 getLogger().debug("Successfully delete repeaters documents for content {} in {} ms", contentId, System.currentTimeMillis() - time_0); 1715 } 1716 1717 /** 1718 * Index all the resources in a given workspace. 1719 * @param workspaceName The workspace where to index 1720 * @param solrClient The solr client to use 1721 * @param progressionTracker The progression of the indexation 1722 * @return The indexation result as a Map. 1723 * @throws Exception if an error occurs while indexing. 1724 */ 1725 public Map<String, Object> indexAllResources(String workspaceName, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 1726 { 1727 Request request = ContextHelper.getRequest(_context); 1728 1729 // Retrieve the current workspace. 1730 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1731 1732 try 1733 { 1734 // Force the workspace. 1735 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 1736 1737 getLogger().info("Starting the indexation of all resources for workspace {}", workspaceName); 1738 1739 long start = System.currentTimeMillis(); 1740 1741 // Delete all resources 1742 _unindexAllResources(workspaceName, solrClient); 1743 1744 Map<String, Object> results = new HashMap<>(); 1745 1746 try 1747 { 1748 TraversableAmetysObject resourceRoot = _resolver.resolveByPath(RepositoryConstants.NAMESPACE_PREFIX + ":resources"); 1749 AmetysObjectIterable<Resource> resources = resourceRoot.getChildren(); 1750 1751 IndexationResult result = doIndexResources(resources, SolrFieldNames.TYPE_RESOURCE, resourceRoot, workspaceName, solrClient, progressionTracker); 1752 1753 long end = System.currentTimeMillis(); 1754 1755 if (!result.hasErrors()) 1756 { 1757 getLogger().info("{} resources indexed without error in {} milliseconds.", result.successCount(), end - start); 1758 } 1759 else 1760 { 1761 getLogger().info("Resource indexation ended, the process took {} milliseconds. {} resources were not indexed successfully, please review the error logs above for more details.", end - start, result.errorCount()); 1762 } 1763 1764 results.put("successCount", result.successCount()); 1765 if (result.hasErrors()) 1766 { 1767 results.put("errorCount", result.errorCount()); 1768 } 1769 } 1770 catch (UnknownAmetysObjectException e) 1771 { 1772 getLogger().info("There is no root for resources in current workspace."); 1773 progressionTracker.setSize(0); 1774 } 1775 1776 return results; 1777 } 1778 catch (Exception e) 1779 { 1780 String error = String.format("Failed to index all resources in workspace %s", workspaceName); 1781 getLogger().error(error, e); 1782 throw new IndexingException(error, e); 1783 } 1784 finally 1785 { 1786 // Restore context 1787 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1788 } 1789 } 1790 1791 /** 1792 * Send a collection of contents for indexation in the solr server. 1793 * @param resources the collection of contents to index. 1794 * @param documentType The document type of the resource 1795 * @param workspaceName The workspace where to index 1796 * @return the indexation result. 1797 * @throws Exception if an error occurs while indexing. 1798 */ 1799 public IndexationResult indexResources(Iterable<AmetysObject> resources, String documentType, String workspaceName) throws Exception 1800 { 1801 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1802 return indexResources(resources, documentType, null, workspaceName, solrClient); 1803 } 1804 1805 /** 1806 * Send a collection of contents for indexation in the solr server. 1807 * @param resources the collection of contents to index. 1808 * @param documentType The document type of the resource 1809 * @param resourceRoot The resource root, can be null. In that case, it will be computed for each resource. 1810 * @param workspaceName The workspace where to index 1811 * @param solrClient The solr client to use 1812 * @return the indexation result. 1813 * @throws Exception if an error occurs while indexing. 1814 */ 1815 public IndexationResult indexResources(Iterable<? extends AmetysObject> resources, String documentType, TraversableAmetysObject resourceRoot, String workspaceName, SolrClient solrClient) throws Exception 1816 { 1817 return indexResources(resources, documentType, resourceRoot, workspaceName, solrClient, ProgressionTrackerFactory.createSimpleProgressionTracker("Index resources", getLogger())); 1818 } 1819 1820 /** 1821 * Send a collection of contents for indexation in the solr server. 1822 * @param resources the collection of contents to index. 1823 * @param documentType The document type of the resource 1824 * @param resourceRoot The resource root, can be null. In that case, it will be computed for each resource. 1825 * @param workspaceName The workspace where to index 1826 * @param solrClient The solr client to use 1827 * @param progressionTracker The progression of the indexation 1828 * @return the indexation result. 1829 * @throws Exception if an error occurs while indexing. 1830 */ 1831 public IndexationResult indexResources(Iterable<? extends AmetysObject> resources, String documentType, TraversableAmetysObject resourceRoot, String workspaceName, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 1832 { 1833 Request request = ContextHelper.getRequest(_context); 1834 1835 // Retrieve the current workspace. 1836 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1837 1838 try 1839 { 1840 // Force the workspace. 1841 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 1842 1843 getLogger().info("Starting indexation of several resources for workspace {}", workspaceName); 1844 1845 long start = System.currentTimeMillis(); 1846 1847 IndexationResult result = doIndexResources(resources, documentType, resourceRoot, workspaceName, solrClient, progressionTracker); 1848 1849 long end = System.currentTimeMillis(); 1850 1851 if (!result.hasErrors()) 1852 { 1853 getLogger().info("{} resources indexed without error in {} milliseconds.", result.successCount(), end - start); 1854 } 1855 else 1856 { 1857 getLogger().info("Resource indexation ended, the process took {} milliseconds. {} resources were not indexed successfully, please review the error logs above for more details.", end - start, result.errorCount()); 1858 } 1859 1860 return result; 1861 } 1862 catch (Exception e) 1863 { 1864 String error = String.format("Failed to index several resources in workspace %s", workspaceName); 1865 getLogger().error(error, e); 1866 throw new IndexingException(error, e); 1867 } 1868 finally 1869 { 1870 // Restore context 1871 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1872 } 1873 } 1874 1875 /** 1876 * Send some resources for indexation in the solr server. 1877 * @param resources the resources to index. 1878 * @param documentType The document type of the resource 1879 * @param resourceRoot The resource root, can be null. In that case, it will be computed for each resource. 1880 * @param workspaceName The workspace where to index 1881 * @param solrClient The solr client to use 1882 * @param progressionTracker The progression of the indexation 1883 * @return the indexation result. 1884 * @throws Exception if an error occurs committing the results. 1885 */ 1886 protected IndexationResult doIndexResources(Iterable<? extends AmetysObject> resources, String documentType, TraversableAmetysObject resourceRoot, String workspaceName, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 1887 { 1888 try 1889 { 1890 // Add callable for each content to index 1891 List<Future<Void>> tasks = new ArrayList<>(); 1892 for (AmetysObject resource : resources) 1893 { 1894 tasks.addAll(_asyncIndexExplorerItem(resource, documentType, resourceRoot, solrClient)); 1895 } 1896 1897 // Now that everything is submitted, we can iterate and wait for result 1898 return IndexationResult.fromTasks(tasks, getLogger()); 1899 } 1900 finally 1901 { 1902 progressionTracker.increment(); 1903 } 1904 } 1905 1906 /** 1907 * Add or update a resource into Solr index 1908 * @param resource The resource to index 1909 * @param documentType The document type of the resource 1910 * @param workspaceName The workspace where to index 1911 * @throws Exception if an error occurs while indexing. 1912 */ 1913 public void indexResource(Resource resource, String documentType, String workspaceName) throws Exception 1914 { 1915 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 1916 doIndexResources(Collections.singleton(resource), documentType, null, workspaceName, solrClient, ProgressionTrackerFactory.createSimpleProgressionTracker("Index resource", getLogger())); 1917 } 1918 1919 private void _unindexAllResources(String workspaceName, SolrClient solrClient) throws Exception 1920 { 1921 getLogger().debug("Unindexing all resources from Solr."); 1922 1923 String collection = _solrClientProvider.getCollectionName(workspaceName); 1924 solrClient.deleteByQuery(collection, SolrFieldNames.DOCUMENT_TYPE + ':' + SolrFieldNames.TYPE_RESOURCE); 1925 1926 getLogger().debug("Succesfully deleted all resource documents from Solr."); 1927 } 1928 1929 /** 1930 * Delete all resource documents at a given path for all workspaces and commit 1931 * @param rootId The resource root ID, must not be null. 1932 * @param path The resource path relative to the given root, must start with a slash. 1933 * @throws Exception If an error occurs while unindexing. 1934 */ 1935 public void unindexResourcesByPath(String rootId, String path) throws Exception 1936 { 1937 String[] workspaceNames = _repository.getWorkspaces(); 1938 for (String workspaceName : workspaceNames) 1939 { 1940 unindexResourcesByPath(rootId, path, workspaceName); 1941 } 1942 } 1943 1944 /** 1945 * Delete all resource documents at a given path. 1946 * @param rootId The resource root ID, must not be null. 1947 * @param path The resource path relative to the given root, must start with a slash. 1948 * @param workspaceName The workspace where to work in 1949 * @throws Exception If an error occurs while unindexing. 1950 */ 1951 public void unindexResourcesByPath(String rootId, String path, String workspaceName) throws Exception 1952 { 1953 Request request = ContextHelper.getRequest(_context); 1954 1955 // Retrieve the current workspace. 1956 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1957 1958 try 1959 { 1960 // Force the workspace. 1961 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 1962 1963 getLogger().debug("Unindexing all resources at path {} in root {}", path, rootId); 1964 1965 Query query = new ResourceLocationQuery(rootId, path); 1966 1967 String collection = _solrClientProvider.getCollectionName(workspaceName); 1968 _getAutoCommitSolrClient(workspaceName).deleteByQuery(collection, query.build()); 1969 1970 // unindexAdditionnalDataIndexer("resources", ???, workspaceName, solrClient); 1971 1972 getLogger().debug("Succesfully deleted resource document from Solr."); 1973 } 1974 catch (Exception e) 1975 { 1976 String error = String.format("Failed to unindex resource %s in workspace %s", path, workspaceName); 1977 getLogger().error(error, e); 1978 throw new IndexingException(error, e); 1979 } 1980 finally 1981 { 1982 // Restore workspace 1983 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1984 } 1985 } 1986 1987 /** 1988 * Remove a resource from Solr index for all workspaces and commit 1989 * @param resourceId The id of resource to unindex 1990 * @throws Exception if an error occurs while indexing. 1991 */ 1992 public void unindexResource(String resourceId) throws Exception 1993 { 1994 String[] workspaceNames = _repository.getWorkspaces(); 1995 for (String workspaceName : workspaceNames) 1996 { 1997 unindexResource(resourceId, workspaceName); 1998 } 1999 } 2000 2001 /** 2002 * Remove a resource from the Solr index. 2003 * @param resourceId The id of resource to unindex 2004 * @param workspaceName The workspace where to work in 2005 * @throws Exception if an error occurs while unindexing. 2006 */ 2007 public void unindexResource(String resourceId, String workspaceName) throws Exception 2008 { 2009 Request request = ContextHelper.getRequest(_context); 2010 2011 // Retrieve the current workspace. 2012 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 2013 2014 try 2015 { 2016 // Force the workspace. 2017 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 2018 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 2019 2020 getLogger().debug("Unindexing resource {} from Solr.", resourceId); 2021 2022 doUnindexDocument(resourceId, workspaceName, solrClient); 2023 unindexAdditionnalData("resource", resourceId, workspaceName, solrClient); 2024 2025 getLogger().debug("Succesfully deleted resource {} from Solr.", resourceId); 2026 } 2027 catch (Exception e) 2028 { 2029 String error = String.format("Failed to unindex resource %s in workspace %s", resourceId, workspaceName); 2030 getLogger().error(error, e); 2031 throw new IndexingException(error, e); 2032 } 2033 finally 2034 { 2035 // Restore workspace 2036 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 2037 } 2038 } 2039 2040 private List<Future<Void>> _asyncIndexExplorerItem(AmetysObject node, String documentType, TraversableAmetysObject resourceRoot, SolrClient solrClient) throws Exception 2041 { 2042 List<Future<Void>> tasks = new ArrayList<>(); 2043 2044 if (node instanceof ResourceCollection resourceCollection) 2045 { 2046 try (AmetysObjectIterable<AmetysObject> children = resourceCollection.getChildren()) 2047 { 2048 for (AmetysObject child : children) 2049 { 2050 tasks.addAll(_asyncIndexExplorerItem(child, documentType, resourceRoot, solrClient)); 2051 } 2052 } 2053 catch (Throwable t) 2054 { 2055 getLogger().error("Failed to index resource collection {}. Skipping it.", resourceCollection.getId(), t); 2056 } 2057 } 2058 else if (node instanceof Resource resource) 2059 { 2060 tasks.add(_threadIndexerHelper.submitCallable(new ResourceIndexerCallable(resource, _workspaceSelector.getWorkspace(), documentType, resourceRoot, solrClient))); 2061 } 2062 2063 return tasks; 2064 } 2065 2066 /** 2067 * Process a Solr commit operation in all workspaces. 2068 * <br>Use this only after a long operation with updates sent via {@link NoAutoCommitUpdateClient} 2069 * @throws SolrServerException if there is an error on the server 2070 * @throws IOException if there is a communication error with the server 2071 */ 2072 public void commit() throws SolrServerException, IOException 2073 { 2074 String[] workspaceNames; 2075 try 2076 { 2077 workspaceNames = _repository.getWorkspaces(); 2078 for (String workspaceName : workspaceNames) 2079 { 2080 SolrClient solrClient = _getNoAutoCommitSolrClient(workspaceName); 2081 commit(workspaceName, solrClient); 2082 } 2083 } 2084 catch (RepositoryException e) 2085 { 2086 throw new RuntimeException("An exception occured while retrieving JCR workspaces. Cannot commited to Solr.", e); 2087 } 2088 } 2089 2090 /** 2091 * Process a Solr commit operation in given workspace. 2092 * @param workspaceName The workspace's name 2093 * @param solrClient The solr client to use 2094 * @throws SolrServerException if there is an error on the server 2095 * @throws IOException if there is a communication error with the server 2096 */ 2097 public void commit(String workspaceName, SolrClient solrClient) throws SolrServerException, IOException 2098 { 2099 long time_0 = System.currentTimeMillis(); 2100 2101 // Commit 2102 UpdateResponse solrResponse = solrClient.commit(_solrClientProvider.getCollectionName(workspaceName)); 2103 int status = solrResponse.getStatus(); 2104 2105 if (status != 0) 2106 { 2107 throw new IOException("Ametys indexing: Solr commit operation - Expecting status code of '0' in the Solr response but got : '" + status + "'."); 2108 } 2109 2110 getLogger().debug("Successful Solr commit operation during an Ametys indexing process in {} ms", System.currentTimeMillis() - time_0); 2111 } 2112 2113 /** 2114 * Launch a solr index optimization. 2115 * @param workspaceName The workspace's name 2116 * @param solrClient The solr client to use 2117 * @throws SolrServerException if there is an error on the server 2118 * @throws IOException if there is a communication error with the server 2119 */ 2120 public void optimize(String workspaceName, SolrClient solrClient) throws SolrServerException, IOException 2121 { 2122 UpdateResponse solrResponse = solrClient.optimize(_solrClientProvider.getCollectionName(workspaceName)); 2123 int status = solrResponse.getStatus(); 2124 2125 if (status != 0) 2126 { 2127 throw new IOException("Ametys indexing: Solr optimize operation - Expecting status code of '0' in the Solr response but got : '" + status + "'."); 2128 } 2129 2130 getLogger().debug("Successful Solr optimize operation during an Ametys indexing process."); 2131 } 2132 2133 /** 2134 * Delete all documents from the solr index. 2135 * @param workspaceName The workspace name 2136 * @param solrClient The solr client to use 2137 * @throws Exception if an error occurs while unindexing. 2138 */ 2139 public void unindexAllDocuments(String workspaceName, SolrClient solrClient) throws Exception 2140 { 2141 getLogger().debug("Deleting all documents from Solr."); 2142 2143 String collection = _solrClientProvider.getCollectionName(workspaceName); 2144 solrClient.deleteByQuery(collection, "*:*"); 2145 2146 getLogger().debug("Successfully deleted all documents from Solr."); 2147 } 2148 2149 /** 2150 * Delete a document from the Solr server. 2151 * @param id The id of the document to delete from Solr 2152 * @param workspaceName The workspace name 2153 * @param solrClient The solr client to use 2154 * @throws Exception if an error occurs while indexing. 2155 */ 2156 protected void doUnindexDocument(String id, String workspaceName, SolrClient solrClient) throws Exception 2157 { 2158 UpdateResponse solrResponse = solrClient.deleteById(_solrClientProvider.getCollectionName(workspaceName), id); 2159 int status = solrResponse.getStatus(); 2160 2161 if (status != 0) 2162 { 2163 throw new IOException("Deletion of document " + id + ": got status code '" + status + "'."); 2164 } 2165 } 2166 2167 /** 2168 * Delete content attachments documents of a given content from the Solr server. 2169 * @param contentId The id of the content 2170 * @param workspaceName The workspace name 2171 * @param solrClient The solr client to use 2172 * @throws Exception if an error occurs while indexing. 2173 */ 2174 protected void doUnindexContentAttachments(String contentId, String workspaceName, SolrClient solrClient) throws Exception 2175 { 2176 String collectionName = _solrClientProvider.getCollectionName(workspaceName); 2177 2178 Query query = new ContentAttachmentQuery(contentId); 2179 UpdateResponse solrResponse = solrClient.deleteByQuery(collectionName, query.build()); 2180 int status = solrResponse.getStatus(); 2181 2182 if (status != 0) 2183 { 2184 throw new IOException("Deletion of content attachments of content " + contentId + ": got status code '" + status + "'."); 2185 } 2186 } 2187 2188 /** 2189 * Add or update a trash element into Solr index on all workspaces and commit 2190 * @param trashElementId The id of the trash element to index 2191 * @throws Exception if an error occurs while indexing. 2192 */ 2193 public void indexTrashElement(String trashElementId) throws Exception 2194 { 2195 String[] workspaceNames = _repository.getWorkspaces(); 2196 for (String workspaceName : workspaceNames) 2197 { 2198 indexTrashElement(trashElementId, workspaceName); 2199 } 2200 } 2201 2202 /** 2203 * Add or update a trash element into Solr index 2204 * @param trashElementId The id of the trash element to index 2205 * @param workspaceName the workspace where to index 2206 * @throws Exception if an error occurs while indexing. 2207 */ 2208 public void indexTrashElement(String trashElementId, String workspaceName) throws Exception 2209 { 2210 Request request = ContextHelper.getRequest(_context); 2211 2212 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 2213 2214 // Retrieve the current workspace. 2215 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 2216 2217 try 2218 { 2219 // Force the workspace. 2220 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 2221 2222 if (_resolver.hasAmetysObjectForId(trashElementId)) 2223 { 2224 DefaultTrashElement trashElement = _resolver.resolveById(trashElementId); 2225 doIndexTrashElements(Collections.singleton(trashElement), workspaceName, solrClient, ProgressionTrackerFactory.createSimpleProgressionTracker("Index trash element", getLogger())); 2226 } 2227 } 2228 finally 2229 { 2230 // Restore context 2231 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 2232 } 2233 } 2234 2235 /** 2236 * Index all the trash elements in a given workspace. 2237 * @param workspaceName the workspace where to index 2238 * @param solrClient The solr client to use 2239 * @param progressionTracker The progression of the indexation 2240 * @return The indexation result as a Map. 2241 * @throws Exception if an error occurs while indexing. 2242 */ 2243 public Map<String, Object> indexAllTrashElements(String workspaceName, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 2244 { 2245 Request request = ContextHelper.getRequest(_context); 2246 2247 // Retrieve the current workspace. 2248 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 2249 2250 try 2251 { 2252 // Force the workspace. 2253 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 2254 2255 getLogger().info("Starting the indexation of all trash elements for workspace {}", workspaceName); 2256 2257 long start = System.currentTimeMillis(); 2258 2259 // Delete all trash elements 2260 unindexAllTrashElements(workspaceName, solrClient); 2261 2262 String query = QueryHelper.getXPathQuery(null, TrashElementFactory.TRASH_ELEMENT_NODETYPE, null); 2263 AmetysObjectIterable<DefaultTrashElement> trashElements = _resolver.query(query); 2264 2265 IndexationResult result = doIndexTrashElements(trashElements, workspaceName, solrClient, progressionTracker); 2266 2267 long end = System.currentTimeMillis(); 2268 2269 if (!result.hasErrors()) 2270 { 2271 getLogger().info("{} trash elements indexed without error in {} milliseconds.", result.successCount(), end - start); 2272 } 2273 else 2274 { 2275 getLogger().info("Trash elements indexation ended, the process took {} milliseconds. {} trash elements were not indexed successfully, please review the error logs above for more details.", end - start, result.errorCount()); 2276 } 2277 2278 Map<String, Object> results = new HashMap<>(); 2279 results.put("successCount", result.successCount()); 2280 if (result.hasErrors()) 2281 { 2282 results.put("errorCount", result.errorCount()); 2283 } 2284 2285 return results; 2286 } 2287 catch (Exception e) 2288 { 2289 String error = String.format("Failed to index all trash elements in workspace %s", workspaceName); 2290 getLogger().error(error, e); 2291 throw new IndexingException(error, e); 2292 } 2293 finally 2294 { 2295 // Restore context 2296 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 2297 } 2298 } 2299 2300 /** 2301 * Remove a trash element from Solr index for all workspaces and commit 2302 * @param trashElementId The id of trash element to unindex 2303 * @throws Exception if an error occurs while indexing. 2304 */ 2305 public void unindexTrashElement(String trashElementId) throws Exception 2306 { 2307 String[] workspaceNames = _repository.getWorkspaces(); 2308 for (String workspaceName : workspaceNames) 2309 { 2310 unindexTrashElement(trashElementId, workspaceName); 2311 } 2312 } 2313 2314 /** 2315 * Remove a trash element from Solr index 2316 * @param trashElementId The id of trash element to unindex 2317 * @param workspaceName The workspace where to work in 2318 * @throws Exception if an error occurs while indexing. 2319 */ 2320 public void unindexTrashElement(String trashElementId, String workspaceName) throws Exception 2321 { 2322 Request request = ContextHelper.getRequest(_context); 2323 2324 SolrClient solrClient = _getAutoCommitSolrClient(workspaceName); 2325 2326 // Retrieve the current workspace. 2327 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 2328 2329 try 2330 { 2331 // Force the workspace. 2332 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 2333 2334 UpdateResponse solrResponse = solrClient.deleteById(_solrClientProvider.getCollectionName(workspaceName), trashElementId); 2335 int status = solrResponse.getStatus(); 2336 2337 if (status != 0) 2338 { 2339 throw new IOException("Deletion of document " + trashElementId + ": got status code '" + status + "'."); 2340 } 2341 2342 unindexResourcesByPath("trashelement", trashElementId, workspaceName); 2343 } 2344 finally 2345 { 2346 // Restore context 2347 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 2348 } 2349 } 2350 2351 /** 2352 * Unindex all trash elements documents. 2353 * @param workspaceName The workspace name 2354 * @param solrClient The solr client to use 2355 * @throws Exception if an error occurs while unindexing. 2356 */ 2357 protected void unindexAllTrashElements(String workspaceName, SolrClient solrClient) throws Exception 2358 { 2359 getLogger().debug("Unindexing all trash elements from Solr."); 2360 2361 String collection = _solrClientProvider.getCollectionName(workspaceName); 2362 Query query = new DocumentTypeQuery(SolrFieldNames.TYPE_TRASH_ELEMENT); 2363 2364 Query unindexAllAdditionnalDataIndexer = unindexAllAdditionnalData("trashelement"); 2365 if (unindexAllAdditionnalDataIndexer != null) 2366 { 2367 query = new OrQuery(query, unindexAllAdditionnalDataIndexer); 2368 } 2369 2370 solrClient.deleteByQuery(collection, query.build()); 2371 2372 getLogger().debug("Succesfully deleted all trash elements documents from Solr."); 2373 } 2374 2375 /** 2376 * Send some trash elements for indexation in the solr server. 2377 * @param trashElements the trash elements to index. 2378 * @param workspaceName The workspace name 2379 * @param solrClient The solr client to use 2380 * @param progressionTracker The progression of the indexation 2381 * @return the indexation result. 2382 * @throws Exception if an error occurs committing the results. 2383 */ 2384 protected IndexationResult doIndexTrashElements(Iterable<DefaultTrashElement> trashElements, String workspaceName, SolrClient solrClient, SimpleProgressionTracker progressionTracker) throws Exception 2385 { 2386 int numberOfTrashElements = IterableUtils.size(trashElements); 2387 progressionTracker.setSize(numberOfTrashElements); 2388 2389 // Add callable for each content to index 2390 List<Future<Void>> tasks = new ArrayList<>(); 2391 for (DefaultTrashElement trashElement : trashElements) 2392 { 2393 tasks.add(_threadIndexerHelper.submitCallable(new TrashElementIndexerCallable(trashElement, workspaceName, solrClient, progressionTracker))); 2394 } 2395 2396 // Now that everything is submitted, we can iterate and wait for result 2397 return IndexationResult.fromTasks(tasks, getLogger()); 2398 } 2399 2400// /** 2401// * Get the collection to use. 2402// * @return The name of the collection to index into. 2403// */ 2404// protected String getCollection() 2405// { 2406// return _solrCorePrefix + _workspaceSelector.getWorkspace(); 2407// } 2408 2409 2410 /** 2411 * Unindex additional data indexed by additional data indexers. 2412 * @param type The type of the additional data indexer 2413 * @param ametysObjectId The id of the ametys object to unindex 2414 * @param workspaceName The workspace where to work in 2415 * @param solrClient The solr client to use 2416 * @throws Exception If an error occurs while unindexing. 2417 */ 2418 protected void unindexAdditionnalData(String type, String ametysObjectId, String workspaceName, SolrClient solrClient) throws Exception 2419 { 2420 String collectionName = _solrClientProvider.getCollectionName(workspaceName); 2421 2422 List<Query> queries = new ArrayList<>(); 2423 2424 Collection<AdditionalDataIndexer> indexers = _additionalDataIndexerEP.getIndexers(type); 2425 for (AdditionalDataIndexer indexer : indexers) 2426 { 2427 queries.addAll(indexer.getUnindexObjectQuery(ametysObjectId)); 2428 } 2429 2430 if (queries.isEmpty()) 2431 { 2432 return; 2433 } 2434 2435 Query query = new OrQuery(queries); 2436 UpdateResponse solrResponse = solrClient.deleteByQuery(collectionName, query.build()); 2437 int status = solrResponse.getStatus(); 2438 2439 if (status != 0) 2440 { 2441 throw new IOException("Deletion of additionnal data of ametys object " + ametysObjectId + ": got status code '" + status + "'."); 2442 } 2443 } 2444 2445 /** 2446 * Get the query to unindex all additional data indexed by additional data indexers for a given type. 2447 * @param type The type of the additional data indexer 2448 * @return The query to unindex all additional data indexed by additional data indexers for the given type. 2449 * @throws Exception If an error occurs while unindexing. 2450 */ 2451 protected Query unindexAllAdditionnalData(String type) throws Exception 2452 { 2453 List<Query> queries = new ArrayList<>(); 2454 2455 Collection<AdditionalDataIndexer> indexers = _additionalDataIndexerEP.getIndexers(type); 2456 for (AdditionalDataIndexer indexer : indexers) 2457 { 2458 queries.add(indexer.getUnindexAllQuery(type)); 2459 } 2460 2461 List<Query> nonNullQueries = queries.stream().filter(Objects::nonNull).toList(); 2462 if (nonNullQueries.isEmpty()) 2463 { 2464 return null; 2465 } 2466 else 2467 { 2468 return new OrQuery(nonNullQueries); 2469 } 2470 } 2471 2472 2473 private static final class SchemaRequestComparator implements Comparator<SchemaRequest.Update> 2474 { 2475 public int compare(SchemaRequest.Update u1, SchemaRequest.Update u2) 2476 { 2477 return Integer.compare(_getOrder(u1), _getOrder(u2)); 2478 } 2479 2480 private int _getOrder(SchemaRequest.Update update) 2481 { 2482 // Field types are needed first to define fields 2483 if (update instanceof SchemaRequest.AddFieldType) 2484 { 2485 return 1; 2486 } 2487 2488 // Fields or dynamic fields can be defined at the same time 2489 if (update instanceof SchemaRequest.AddField || update instanceof SchemaRequest.AddDynamicField) 2490 { 2491 return 2; 2492 } 2493 2494 // Copy fields can be based on fields or dynamic fields 2495 if (update instanceof SchemaRequest.AddCopyField) 2496 { 2497 return 3; 2498 } 2499 2500 return 0; 2501 } 2502 } 2503 2504 private class ContentIndexerCallable extends AbstractIndexerCallable<Content> 2505 { 2506 private boolean _indexAttachments; 2507 private SimpleProgressionTracker _tracker; 2508 2509 @SuppressWarnings("synthetic-access") 2510 public ContentIndexerCallable(Content content, String workspaceName, boolean indexAttachments, SolrClient solrClient, SimpleProgressionTracker progressionTracker) 2511 { 2512 super(content, workspaceName, solrClient, _manager, _cocoonContext, _resolver, getLogger()); 2513 this._indexAttachments = indexAttachments; 2514 this._tracker = progressionTracker; 2515 } 2516 2517 @Override 2518 protected void process(Content content) throws Exception 2519 { 2520 try 2521 { 2522 doIndexContent(content, _workspaceName, _solrClient); 2523 doIndexContentWorkflow(content, _workspaceName, _solrClient); 2524 if (_indexAttachments) 2525 { 2526 indexContentAttachments(content.getRootAttachments(), content, _solrClient); 2527 } 2528 } 2529 finally 2530 { 2531 _tracker.increment(); 2532 } 2533 } 2534 2535 @Override 2536 protected String getObjectLabel() 2537 { 2538 return "content"; 2539 } 2540 } 2541 2542 private class ResourceIndexerCallable extends AbstractIndexerCallable<Resource> 2543 { 2544 private String _documentType; 2545 private TraversableAmetysObject _resourceRoot; 2546 2547 @SuppressWarnings("synthetic-access") 2548 public ResourceIndexerCallable(Resource resource, String workspaceName, String documentType, TraversableAmetysObject resourceRoot, SolrClient solrClient) 2549 { 2550 super(resource, workspaceName, solrClient, _manager, _cocoonContext, _resolver, getLogger()); 2551 this._documentType = documentType; 2552 this._resourceRoot = resourceRoot; 2553 } 2554 2555 @Override 2556 protected void process(Resource resource) throws Exception 2557 { 2558 SolrInputDocument document = new SolrInputDocument(); 2559 2560 List<SolrInputDocument> additionalDocuments = _solrResourceIndexer.indexResource(resource, document, _documentType, _resourceRoot); 2561 List<SolrInputDocument> documents = new ArrayList<>(additionalDocuments); 2562 documents.add(document); 2563 2564 UpdateResponse solrResponse = _solrClient.add(_solrClientProvider.getCollectionName(_workspaceName), documents); 2565 int status = solrResponse.getStatus(); 2566 2567 if (status != 0) 2568 { 2569 throw new IOException("Resource indexation: got status code '" + status + "'."); 2570 } 2571 } 2572 2573 @Override 2574 protected String getObjectLabel() 2575 { 2576 return "resource"; 2577 } 2578 } 2579 2580 private class TrashElementIndexerCallable extends AbstractIndexerCallable<DefaultTrashElement> 2581 { 2582 private SimpleProgressionTracker _tracker; 2583 2584 @SuppressWarnings("synthetic-access") 2585 public TrashElementIndexerCallable(DefaultTrashElement trashElement, String workspaceName, SolrClient solrClient, SimpleProgressionTracker progressionTracker) 2586 { 2587 super(trashElement, workspaceName, solrClient, _manager, _cocoonContext, _resolver, getLogger()); 2588 this._tracker = progressionTracker; 2589 } 2590 2591 @Override 2592 protected void process(DefaultTrashElement trashElement) throws Exception 2593 { 2594 try 2595 { 2596 SolrInputDocument document = new SolrInputDocument(); 2597 List<SolrInputDocument> additionalDocuments = _solrTrashElementIndexer.indexTrashElement(trashElement, document); 2598 2599 List<SolrInputDocument> documents = new ArrayList<>(additionalDocuments); 2600 documents.add(document); 2601 2602 UpdateResponse solrResponse = _solrClient.add(_solrClientProvider.getCollectionName(_workspaceName), documents); 2603 int status = solrResponse.getStatus(); 2604 2605 if (status != 0) 2606 { 2607 throw new IOException("Trash element indexation: got status code '" + status + "'."); 2608 } 2609 } 2610 finally 2611 { 2612 _tracker.increment(); 2613 } 2614 } 2615 2616 @Override 2617 protected String getObjectLabel() 2618 { 2619 return "trashElement"; 2620 } 2621 } 2622}