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