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.search.solr;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.Optional;
021import java.util.concurrent.TimeUnit;
022
023import javax.jcr.RepositoryException;
024
025import org.apache.avalon.framework.activity.Disposable;
026import org.apache.avalon.framework.activity.Initializable;
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.io.IOUtils;
032import org.apache.commons.lang3.ArrayUtils;
033import org.apache.solr.client.solrj.SolrClient;
034import org.apache.solr.client.solrj.impl.Http2SolrClient;
035
036import org.ametys.plugins.repository.provider.AbstractRepository;
037import org.ametys.plugins.repository.provider.JackrabbitRepository;
038import org.ametys.plugins.repository.provider.WorkspaceSelector;
039import org.ametys.runtime.config.Config;
040import org.ametys.runtime.plugin.component.AbstractLogEnabled;
041
042/**
043 * Component acting as a single entry point to get access to Solr clients.
044 */
045public class DefaultSolrClientProvider extends AbstractLogEnabled implements SolrClientProvider, Component, Serviceable, Initializable, Disposable
046{
047    private static final String __SOLR_URL_CONFIG = "cms.solr.core.url"; 
048    private static final String __SOLR_SOCKET_TIMEOUT_CONFIG = "cms.solr.socket.timeout";
049    private static final String __SOLR_CORE_PREFIX_CONFIG = "cms.solr.core.prefix";  
050    
051    /** The workspace selector. */
052    protected WorkspaceSelector _workspaceSelector;
053    
054    /** The JCR repository */
055    protected JackrabbitRepository _repository;
056
057    /** The Solr read client. */
058    protected SolrClient _solrReadClient;
059    
060    /** The Solr "default" update clients, per workspace */
061    protected Map<String, SolrClient> _solrDefaultUpdateClients;
062    
063    /** The Solr "no auto commit" update clients, per workspace */
064    protected Map<String, SolrClient> _solrNoAutoCommitUpdateClients;
065
066    /** The solr URL. */
067    protected String _solrUrl;
068    
069    /** The solr socket timeout (in millis). */
070    protected Optional<Integer> _solrSocketTimeout;
071    
072    /** The solr core prefix. */
073    protected String _solrCorePrefix;
074    
075    @Override
076    public void service(ServiceManager serviceManager) throws ServiceException
077    {
078        _workspaceSelector = (WorkspaceSelector) serviceManager.lookup(WorkspaceSelector.ROLE);
079        _repository = (JackrabbitRepository) serviceManager.lookup(AbstractRepository.ROLE);
080    }
081    
082    @Override
083    public void initialize() throws Exception
084    {
085        _solrUrl = Config.getInstance().getValue(__SOLR_URL_CONFIG);
086        _solrSocketTimeout = Optional.of(__SOLR_SOCKET_TIMEOUT_CONFIG)
087                .map(Config.getInstance()::<Long>getValue)
088                .map(Long::intValue)
089                .map(s -> s * 1000);
090        _solrCorePrefix = Config.getInstance().getValue(__SOLR_CORE_PREFIX_CONFIG);
091        
092        Http2SolrClient.Builder solrReadClientBuilder = new Http2SolrClient.Builder(_solrUrl);
093        if (_solrSocketTimeout.isPresent())
094        {
095            solrReadClientBuilder.withRequestTimeout(_solrSocketTimeout.get(), TimeUnit.MILLISECONDS);
096        }
097        _solrReadClient = solrReadClientBuilder.build();
098        
099        String[] workspaces = _repository.getWorkspaces();
100        _solrDefaultUpdateClients = new HashMap<>();
101        _solrNoAutoCommitUpdateClients = new HashMap<>();
102        for (String workspaceName : workspaces)
103        {
104            _solrDefaultUpdateClients.put(workspaceName, _createDefaultUpdateClient(workspaceName));
105            _solrNoAutoCommitUpdateClients.put(workspaceName, _createNoAutoCommitUpdateClient(workspaceName));
106        }
107    }
108    
109    private SolrClient _createDefaultUpdateClient(String workspaceName)
110    {
111        return new DefaultUpdateClient(_solrUrl, _solrSocketTimeout, getCollectionName(workspaceName), 10, 4, getLogger());
112    }
113    
114    private SolrClient _createNoAutoCommitUpdateClient(String workspaceName)
115    {
116        return new NoAutoCommitUpdateClient(_solrUrl, _solrSocketTimeout, getCollectionName(workspaceName), 10, 4, getLogger());
117    }
118    
119    @Override
120    public void dispose()
121    {
122        // Release the solr clients (as a Closeable).
123        IOUtils.closeQuietly(_solrReadClient);
124        _solrReadClient = null;
125        
126        for (SolrClient solrDefaultUpdateClient : _solrDefaultUpdateClients.values())
127        {
128            IOUtils.closeQuietly(solrDefaultUpdateClient);
129        }
130        _solrDefaultUpdateClients.clear();
131        _solrDefaultUpdateClients = null;
132        
133        for (SolrClient solrNoAutoCommitUpdateClient : _solrNoAutoCommitUpdateClients.values())
134        {
135            IOUtils.closeQuietly(solrNoAutoCommitUpdateClient);
136        }
137        _solrNoAutoCommitUpdateClients.clear();
138        _solrNoAutoCommitUpdateClients = null;
139    }
140    
141    @Override
142    public SolrClient getReadClient()
143    {
144        return _solrReadClient;
145    }
146    
147    @Override
148    public SolrClient getUpdateClient(String workspaceName, boolean autoCommit)
149    {
150        Map<String, SolrClient> updateClients = autoCommit ? _solrDefaultUpdateClients : _solrNoAutoCommitUpdateClients;
151        
152        SolrClient updateClient = updateClients.get(_nonNullWorkspaceName(workspaceName));
153        if (updateClient == null)
154        {
155            // Perhaps the workspace was created after initializing this component, try to check if JCR workspace exist
156            try
157            {
158                if (ArrayUtils.contains(_repository.getWorkspaces(), workspaceName))
159                {
160                    updateClient = autoCommit ? _createDefaultUpdateClient(workspaceName) : _createNoAutoCommitUpdateClient(workspaceName);
161                    updateClients.put(workspaceName, updateClient);
162                }
163            }
164            catch (RepositoryException e)
165            {
166                getLogger().error("An error occurs while trying to return all JCR workspaces", e);
167            }
168        }
169        
170        return updateClient;
171    }
172    
173    @Override
174    public String getCollectionName()
175    {
176        return getCollectionName(_workspaceSelector.getWorkspace());
177    }
178    
179    @Override
180    public String getCollectionName(String workspaceName)
181    {
182        return _solrCorePrefix + _nonNullWorkspaceName(workspaceName);
183    }
184    
185    private String _nonNullWorkspaceName(String workspaceName)
186    {
187        if (workspaceName == null)
188        {
189            getLogger().debug("Passing null workspace name. Switching to current workspace.");
190            return _workspaceSelector.getWorkspace();
191        }
192        else
193        {
194            return workspaceName;
195        }
196    }
197}