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