View Javadoc
1   /*
2    * Copyright (C) 2003-2013 eXo Platform SAS.
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU Affero General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (at your option) any later version.
8    *
9    * This program 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
12   * GNU Affero General Public License for more details.
13   *
14   * You should have received a copy of the GNU Affero General Public License
15   * along with this program. If not, see <http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.services.wcm.search.connector;
18  
19  import java.text.DateFormat;
20  import java.text.Normalizer;
21  import java.text.SimpleDateFormat;
22  import java.util.*;
23  
24  import javax.jcr.Node;
25  import javax.jcr.RepositoryException;
26  
27  import org.exoplatform.commons.api.search.SearchServiceConnector;
28  import org.exoplatform.commons.api.search.data.SearchContext;
29  import org.exoplatform.commons.api.search.data.SearchResult;
30  import org.exoplatform.container.ExoContainerContext;
31  import org.exoplatform.container.xml.InitParams;
32  import org.exoplatform.portal.config.UserPortalConfig;
33  import org.exoplatform.portal.config.UserPortalConfigService;
34  import org.exoplatform.portal.mop.SiteKey;
35  import org.exoplatform.portal.mop.user.UserNavigation;
36  import org.exoplatform.portal.mop.user.UserPortalContext;
37  import org.exoplatform.services.cms.documents.DocumentService;
38  import org.exoplatform.services.cms.drives.DriveData;
39  import org.exoplatform.services.cms.drives.ManageDriveService;
40  import org.exoplatform.services.cms.impl.Utils;
41  import org.exoplatform.services.jcr.RepositoryService;
42  import org.exoplatform.services.log.ExoLogger;
43  import org.exoplatform.services.log.Log;
44  import org.exoplatform.services.security.ConversationState;
45  import org.exoplatform.services.security.IdentityConstants;
46  import org.exoplatform.services.wcm.core.NodetypeConstant;
47  import org.exoplatform.services.wcm.search.QueryCriteria;
48  import org.exoplatform.services.wcm.search.ResultNode;
49  import org.exoplatform.services.wcm.search.SiteSearchService;
50  import org.exoplatform.services.wcm.search.base.AbstractPageList;
51  import org.exoplatform.services.wcm.search.base.EcmsSearchResult;
52  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
53  
54  /**
55   * This abstract class is extended by the SearchService connectors which provide search result for a specific content type
56   */
57  public abstract class BaseSearchServiceConnector extends SearchServiceConnector {
58  
59    public static final String sortByDate = "date";
60    public static final String sortByRelevancy = "relevancy";
61    public static final String sortByTitle = "title";
62    
63    protected SiteSearchService siteSearch_;
64    protected DocumentService documentService;
65    protected ManageDriveService driveService_;
66  
67    private static final Log LOG = ExoLogger.getLogger(BaseSearchServiceConnector.class.getName());
68    
69    public static final String DEFAULT_SITENAME = "intranet";
70    public static final String PAGE_NAGVIGATION = "documents";
71    public static final String NONE_NAGVIGATION = "#";
72    public static final String PORTLET_NAME = "FileExplorerPortlet";
73    
74    public BaseSearchServiceConnector(InitParams initParams) throws Exception {
75      super(initParams);
76      siteSearch_ = WCMCoreUtils.getService(SiteSearchService.class);
77      documentService = WCMCoreUtils.getService(DocumentService.class);
78      driveService_ = WCMCoreUtils.getService(ManageDriveService.class);
79    }
80  
81    /**
82     * The connectors must implement this search method, with the following parameters and return a collection of SearchResult
83     *
84     * @param context Search context
85     * @param query The user-input query to search for
86     * @param sites Search on these specified sites only (e.g acme, intranet...)
87     * @param offset Start offset of the result set
88     * @param limit Maximum size of the result set
89     * @param sort The field to sort the result set
90     * @param order Sort order (ASC, DESC)
91     * @return A collection of SearchResult
92     */
93    @Override
94    public Collection<SearchResult> search(SearchContext context,
95                                           String query,
96                                           Collection<String> sites,
97                                           int offset,
98                                           int limit,
99                                           String sort,
100                                          String order) {
101     Collection<SearchResult> ret = new ArrayList<SearchResult>();
102     //prepare input parameters for search
103     if (query != null) {
104       query = query.trim();
105     }
106     QueryCriteria criteria = createQueryCriteria(query, offset, limit, sort, order);
107     //query search result
108     try {
109         criteria.setSiteName(getSitesStr(sites));
110         ret = convertResult(searchNodes(criteria, context), limit, offset, context);
111     } catch (Exception e) {
112       if (LOG.isErrorEnabled()) {
113         LOG.error(e.getMessage(), e);
114       }
115     }
116     return ret;
117   }
118   
119   /**
120    * convert Collections<String> to string, elements are seperated by commas
121    * @param sites the collection
122    * @return the string
123    */
124   private String getSitesStr(Collection<String> sites) {
125     if (sites == null || sites.size() == 0) return null;
126     StringBuffer s = new StringBuffer();
127     for (String site : sites) {
128       s.append(site).append(',');
129     }
130     return s.substring(0, s.length() - 1);
131   }
132 
133   /**
134    * creates the QueryCriteria object based on the search service
135    * @param query the query string
136    * @param offset the offset
137    * @param limit the limit
138    * @param sort sort field
139    * @param order order by
140    * @return the QueryCriteria 
141    */
142   protected abstract QueryCriteria createQueryCriteria(String query, long offset, long limit, String sort, String order);
143   
144   /**
145    * searches base on the search service type
146    * @param criteria the query criteria
147    * @return page list containing the result
148    */
149   protected abstract AbstractPageList<ResultNode> searchNodes(QueryCriteria criteria, SearchContext context) throws Exception;
150   
151   /**
152    * filters the node base on search type: document or file
153    * @return the node object
154    */
155   protected abstract ResultNode filterNode(ResultNode node) throws RepositoryException;
156   
157   /**
158    * converts data: from {@code PageList<ResultNode> to List<SearchResult>}
159    * @param pageList
160    * @return
161    */
162   protected List<SearchResult> convertResult(AbstractPageList<ResultNode> pageList, int limit, int offset, SearchContext context) {
163     List<SearchResult> ret = new ArrayList<SearchResult>();
164     try {
165       if (pageList != null) {
166         for (int i = 1; i <= pageList.getAvailablePage(); i++) {
167           List<ResultNode> list = pageList.getPageWithOffsetCare(i);
168           if (list == null || list.size() == 0) return ret;
169           for (Object obj : list) {
170             try {
171               if (obj instanceof ResultNode) {
172                 ResultNode retNode = filterNode((ResultNode)obj);
173                 if (retNode == null) {
174                   continue;
175                 }
176                 //generate SearchResult object
177                 Calendar date = getDate(retNode);
178                 String url = getPath(retNode, context);
179                 if (url == null) continue;
180 
181                 EcmsSearchResult result = 
182                 //  new SearchResult(url, title, excerpt, detail, imageUrl, date, relevancy);
183                     new EcmsSearchResult(url,
184                                          getPreviewUrl(retNode, context),
185                                          getTitleResult(retNode), 
186                                          retNode.getExcerpt(), 
187                                          getDetails(retNode, context),
188                                          getImageUrl(retNode), 
189                                          date.getTimeInMillis(), 
190                                          (long)retNode.getScore(),
191                                          getFileType(retNode),
192                                          retNode.getPath());
193                 if (result != null) {
194                   ret.add(result);
195                 }
196                 if (ret.size() >= limit) {
197                   return ret;
198                 }
199               }//if
200             } catch (Exception e) {
201               if (LOG.isErrorEnabled()) {
202                 LOG.error("Failed to get result information.", e);
203               }
204             }
205           }//for inner
206         } //for outer
207       }//if
208     } catch (Exception e) {
209       if (LOG.isErrorEnabled()) {
210         LOG.error(e.getMessage(), e);
211       }
212     }
213     return ret;
214   }
215   
216   /**---------------------------HELPER METHODS----------------------------------*/
217 
218   /**
219    * gets the file size 
220    * @param node The node
221    * @return the file size
222    * @throws Exception
223    */
224   protected String fileSize(Node node) throws Exception {
225     return Utils.fileSize(node);
226   }
227   
228   /**
229    * returns the date information of node
230    * @param node the node
231    * @return the date
232    * @throws Exception
233    */
234   protected Calendar getDate(Node node) throws Exception {
235     return node.hasProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE) ? 
236                  node.getProperty(NodetypeConstant.EXO_LAST_MODIFIED_DATE).getDate() :
237                  node.hasProperty(NodetypeConstant.EXO_DATE_CREATED) ?
238                  node.getProperty(NodetypeConstant.EXO_DATE_CREATED).getDate() :
239                  Calendar.getInstance();
240   }
241 
242   /**
243    * formats the date object in simple date format
244    * @param date the Date object
245    * @return the String representation
246    */
247   protected String formatDate(Calendar date) {
248     DateFormat format = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.FULL, SimpleDateFormat.SHORT);
249     return " - " + format.format(date.getTime());
250   }
251   
252   protected String getDriveTitle(DriveData driveData) {
253     if (driveData == null) {
254       return "";
255     }
256     String id = driveData.getName();
257     String path = driveData.getResolvedHomePath();
258     //get space name (in case drive is space drive)
259     try {
260       Class spaceServiceClass = Class.forName("org.exoplatform.social.core.space.spi.SpaceService");
261       Object spaceService = ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(spaceServiceClass);
262       
263       Class spaceClass = Class.forName("org.exoplatform.social.core.space.model.Space");
264       Object space = spaceServiceClass.getDeclaredMethod("getSpaceByGroupId", String.class)
265                                       .invoke(spaceService, id.replace(".", "/"));
266       if (space != null) {
267         return String.valueOf(spaceClass.getDeclaredMethod("getDisplayName").invoke(space));
268       }
269     } catch (Exception e) {
270       //can not get space, do nothing, will return the drive label
271       if (LOG.isInfoEnabled()) {
272         LOG.info("Can not find the space corresponding to drive " + id);
273       }
274     }
275     //get drive label
276     try {
277       RepositoryService repoService = WCMCoreUtils.getService(RepositoryService.class);
278       Node groupNode = (Node)WCMCoreUtils.getSystemSessionProvider().getSession(
279                                     repoService.getCurrentRepository().getConfiguration().getDefaultWorkspaceName(),
280                                     repoService.getCurrentRepository()).getItem(path);
281       while (groupNode.getParent() != null) {
282         if (groupNode.hasProperty(NodetypeConstant.EXO_LABEL)) {
283           return groupNode.getProperty(NodetypeConstant.EXO_LABEL).getString();
284         } else groupNode = groupNode.getParent();
285       }
286       return id.replace(".", " / ");
287     } catch(Exception e) {
288       return id.replace(".", " / ");
289     }
290   }
291 
292   /**
293    * Remove accents from query
294    * 
295    * @param query
296    * @return
297    */
298   protected static String removeAccents(String query) {
299     query = Normalizer.normalize(query, Normalizer.Form.NFD);
300     query = query.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
301     return query;
302   }
303 
304   /**
305    * returns path of node in format: "{drivename}/{relative path from drive root node}
306    * @param node the node
307    * @return the expected path
308    * @throws Exception
309    */
310   protected abstract String getPath(ResultNode node, SearchContext context) throws Exception;
311 
312   /**
313    * returns the preview url
314    * @param node the node
315    * @return the expected path
316    * @throws Exception
317    */
318   protected String getPreviewUrl(ResultNode node, SearchContext context) throws Exception {
319     // defaults to the same url returned by getPath
320     return getPath(node, context);
321   }
322 
323   /**
324    * gets the file type
325    * @return
326    * @throws Exception
327    */
328   protected abstract String getFileType(ResultNode node) throws Exception;
329   
330   /**
331    * gets the title of result, based on the result type
332    * @param node
333    * @return
334    * @throws Exception
335    */
336   protected abstract String getTitleResult(ResultNode node) throws Exception;
337   
338   /**
339    * gets the image url
340    * @return
341    */
342   protected abstract String getImageUrl(Node node);
343   
344   /**
345    * gets the detail about result node
346    * @param node the node
347    * @throws Exception
348    */
349   protected abstract String getDetails(ResultNode node, SearchContext context) throws Exception;
350 
351 }