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