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