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