View Javadoc
1   /*******************************************************************************
2    *   Gisgraphy Project 
3    * 
4    *   This library is free software; you can redistribute it and/or
5    *   modify it under the terms of the GNU Lesser General Public
6    *   License as published by the Free Software Foundation; either
7    *   version 2.1 of the License, or (at your option) any later version.
8    * 
9    *   This library is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   *   Lesser General Public License for more details.
13   * 
14   *   You should have received a copy of the GNU Lesser General Public
15   *   License along with this library; if not, write to the Free Software
16   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
17   * 
18   *  Copyright 2008  Gisgraphy project 
19   *  David Masclet <davidmasclet@gisgraphy.com>
20   *  
21   *  
22   *******************************************************************************/
23  package com.gisgraphy.fulltext;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.OutputStream;
27  import java.io.UnsupportedEncodingException;
28  import java.net.MalformedURLException;
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  import org.apache.commons.httpclient.HttpClient;
33  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
34  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
35  import org.apache.solr.client.solrj.SolrServerException;
36  import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
37  import org.apache.solr.client.solrj.response.QueryResponse;
38  import org.apache.solr.common.SolrDocument;
39  import org.apache.solr.common.params.ModifiableSolrParams;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  import org.springframework.beans.factory.annotation.Autowired;
43  import org.springframework.beans.factory.annotation.Qualifier;
44  import org.springframework.beans.factory.annotation.Required;
45  import org.springframework.util.Assert;
46  
47  import com.gisgraphy.domain.geoloc.entity.GisFeature;
48  import com.gisgraphy.domain.repository.GisFeatureDao;
49  import com.gisgraphy.domain.valueobject.Constants;
50  import com.gisgraphy.geoloc.ZipcodeNormalizer;
51  import com.gisgraphy.service.IStatsUsageService;
52  import com.gisgraphy.service.ServiceException;
53  import com.gisgraphy.stats.StatsUsageType;
54  
55  /**
56   * Default (threadsafe) implementation of {@link IFullTextSearchEngine}
57   * 
58   * @author <a href="mailto:david.masclet@gisgraphy.com">David Masclet</a>
59   */
60  public class FullTextSearchEngine implements IFullTextSearchEngine {
61  	
62  	
63  	/**
64  	 * very usefull when import is running
65  	 */
66  	public static boolean disableLogging=false;
67  	
68  
69      protected static final Logger logger = LoggerFactory
70  	    .getLogger(FullTextSearchEngine.class);
71  
72      private HttpClient httpClient;
73  
74      private IsolrClient solrClient;
75      
76      FulltextResultDtoBuilder builder = new FulltextResultDtoBuilder();
77  
78      @Autowired
79      @Qualifier("gisFeatureDao")
80      private GisFeatureDao gisFeatureDao;
81  
82      @Autowired
83      IStatsUsageService statsUsageService;
84  
85      /**
86       * Default constructor needed by cglib and spring
87       */
88      @SuppressWarnings("unused")
89      private FullTextSearchEngine() {
90  	super();
91      }
92  
93      /**
94       * @param multiThreadedHttpConnectionManager
95       *                The
96       * @link {@link MultiThreadedHttpConnectionManager} that the fulltext search
97       *       engine will use
98       * @throws FullTextSearchException
99       *                 If an error occured
100      */
101     @Autowired
102     public FullTextSearchEngine(
103 	    @Qualifier("multiThreadedHttpConnectionManager")
104 	    MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager)
105 	    throws FullTextSearchException {
106 	Assert.notNull(multiThreadedHttpConnectionManager,
107 		"multiThreadedHttpConnectionManager can not be null");
108 	HttpConnectionManagerParams p = new HttpConnectionManagerParams();
109 	p.setSoTimeout(0);
110 	p.setConnectionTimeout(0);
111 	multiThreadedHttpConnectionManager.setParams(p);
112 	this.httpClient = new HttpClient(multiThreadedHttpConnectionManager);
113 	if (this.httpClient == null) {
114 	    throw new FullTextSearchException(
115 		    "Can not instanciate http client with multiThreadedHttpConnectionManager : "
116 			    + multiThreadedHttpConnectionManager);
117 	}
118     }
119 
120     /*
121      * (non-Javadoc)
122      * 
123      * @see com.gisgraphy.domain.geoloc.service.fulltextsearch.IFullTextSearchEngine#executeAndSerialize(com.gisgraphy.domain.geoloc.service.fulltextsearch.FulltextQuery,
124      *      java.io.OutputStream)
125      */
126     public void executeAndSerialize(FulltextQuery query,
127 	    OutputStream outputStream) throws FullTextSearchException {
128 	statsUsageService.increaseUsage(StatsUsageType.FULLTEXT);
129 	Assert.notNull(query, "Can not execute a null query");
130 	Assert.notNull(outputStream,
131 		"Can not serialize into a null outputStream");
132 	String queryString = ZipcodeNormalizer.normalize(query.getQuery(), query.getCountryCode());
133 	query.withQuery(queryString);
134 	try {
135 		if (!disableLogging){
136 			logger.info(query.toString());
137 		}
138 
139 	    ModifiableSolrParams params = FulltextQuerySolrHelper.parameterize(query);
140 	    CommonsHttpSolrServer server = new CommonsHttpSolrServer(solrClient
141 		    .getURL(), this.httpClient,
142 		    new OutputstreamResponseWrapper(outputStream, params
143 			    .get(Constants.OUTPUT_FORMAT_PARAMETER)));
144 	    server.query(params);
145 	} catch (SolrServerException e) {
146 	    logger.error("Can not execute query " + FulltextQuerySolrHelper.toQueryString(query)
147 		    + "for URL : " + solrClient.getURL() + " : "
148 		    + e.getCause().getMessage(),e);
149 	    throw new FullTextSearchException(e.getCause().getMessage());
150 	} catch (MalformedURLException e1) {
151 	    logger.error("The URL " + solrClient.getURL() + " is incorrect",e1);
152 	    throw new FullTextSearchException(e1);
153 	} catch (RuntimeException e2) {
154 	    String message = e2.getCause()!=null?e2.getCause().getMessage():e2.getMessage();
155 	    logger
156 		    .error("An error has occurred during fulltext search of query "
157 			    + query + " : " + message,e2);
158 	    throw new FullTextSearchException(message,e2);
159 	}
160 
161     }
162 
163     /*
164      * (non-Javadoc)
165      * 
166      * @see com.gisgraphy.domain.geoloc.service.fulltextsearch.IFullTextSearchEngine#executeQueryToString(com.gisgraphy.domain.geoloc.service.fulltextsearch.FulltextQuery)
167      */
168     public String executeQueryToString(FulltextQuery query) {
169 	try {
170 	    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
171 	    executeAndSerialize(query, outputStream);
172 	    return outputStream.toString(Constants.CHARSET);
173 	} catch (UnsupportedEncodingException e) {
174 	    throw new FullTextSearchException("Encoding error during search : "
175 		    + e.getCause().getMessage(),e);
176 	}
177     }
178 
179     /*
180      * (non-Javadoc)
181      * 
182      * @see com.gisgraphy.domain.geoloc.service.fulltextsearch.IFullTextSearchEngine#executeQuery(com.gisgraphy.domain.geoloc.service.fulltextsearch.FulltextQuery)
183      */
184     public List<? extends GisFeature> executeQueryToDatabaseObjects(
185 	    FulltextQuery query) throws ServiceException {
186 	statsUsageService.increaseUsage(StatsUsageType.FULLTEXT);
187 	Assert.notNull(query, "Can not execute a null query");
188 	String queryString = ZipcodeNormalizer.normalize(query.getQuery(), query.getCountryCode());
189 	query.withQuery(queryString);
190 	ModifiableSolrParams params = FulltextQuerySolrHelper.parameterize(query);
191 	List<GisFeature> gisFeatureList = new ArrayList<GisFeature>();
192 	QueryResponse results = null;
193 	try {
194 	    results = solrClient.getServer().query(params);
195 	    if (!disableLogging){
196 	    	logger.info(query + " took " + (results.getQTime())
197 		    + " ms and returns " + results.getResults().getNumFound()
198 		    + " results");
199 	    }
200 
201 	    List<Long> ids = new ArrayList<Long>();
202 	    for (SolrDocument doc : results.getResults()) {
203 		ids.add((Long) doc.getFieldValue(FullTextFields.FEATUREID
204 			.getValue()));
205 	    }
206 
207 	    gisFeatureList = gisFeatureDao.listByFeatureIds(ids);
208 	} catch (Exception e) {
209 	    logger
210 		    .error("An error has occurred during fulltext search to database object for query "
211 			    + query + " : " + e.getCause().getMessage(),e);
212 	    throw new FullTextSearchException(e);
213 	}
214 
215 	return gisFeatureList;
216     }
217 
218     public FulltextResultsDto executeQuery(FulltextQuery query)
219 	    throws ServiceException {
220 	statsUsageService.increaseUsage(StatsUsageType.FULLTEXT);
221 	Assert.notNull(query, "Can not execute a null query");
222 	String queryString = ZipcodeNormalizer.normalize(query.getQuery(), query.getCountryCode());
223 	query.withQuery(queryString);
224 	ModifiableSolrParams params = FulltextQuerySolrHelper.parameterize(query);
225 	QueryResponse response = null;
226 	try {
227 	    response = solrClient.getServer().query(params);
228 	} catch (SolrServerException e) {
229 	    throw new FullTextSearchException(e.getMessage(), e);
230 	} catch (RuntimeException e) {
231 	    throw new FullTextSearchException(e.getMessage(), e);
232 	}
233 	if (response != null) {
234 	    long numberOfResults = response.getResults() != null ? response
235 		    .getResults().getNumFound() : 0;
236 		    if (!disableLogging){
237 		    	logger.info(query + " took " + response.getQTime()
238 		    	+ " ms and returns " + numberOfResults + " results");
239 		    }
240 	    return builder.build(response);
241 	} else {
242 	    return new FulltextResultsDto();
243 	}
244     }
245 
246     /*
247      * (non-Javadoc)
248      * 
249      * @see com.gisgraphy.domain.geoloc.service.fulltextsearch.IFullTextSearchEngine#isAlive()
250      */
251     public boolean isAlive() {
252 	return solrClient == null ? false : solrClient.isServerAlive();
253     }
254 
255     /**
256      * @param solrClient
257      *                the solrClient to set
258      */
259     @Required
260     public void setSolrClient(IsolrClient solrClient) {
261 	this.solrClient = solrClient;
262     }
263 
264     /*
265      * (non-Javadoc)
266      * 
267      * @see com.gisgraphy.domain.geoloc.service.fulltextsearch.IFullTextSearchEngine#getURL()
268      */
269     public String getURL() {
270 	return solrClient.getURL();
271     }
272 
273 }