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